É considerado um anti-padrão escrever SQL no código-fonte?

87

É considerado um anti-padrão codificar o SQL em um aplicativo como este:

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, eu teria uma camada de repositório, etc., mas a excluí no código acima por questões de simplicidade.

Recentemente, recebi alguns comentários de um colega que reclamou que o SQL estava escrito no código-fonte. Não tive chance de perguntar por que e ele está ausente por duas semanas (talvez mais). Suponho que ele quis dizer:

  1. Use LINQ
    ou
  2. Use procedimentos armazenados para o SQL

Estou correcto? É considerado um anti-padrão escrever SQL no código-fonte? Somos uma pequena equipe trabalhando neste projeto. Acho que o benefício dos procedimentos armazenados é que os desenvolvedores do SQL podem se envolver com o processo de desenvolvimento (escrevendo procedimentos armazenados, etc.).

Editar O link a seguir fala sobre instruções SQL codificadas: https://docs.microsoft.com/en-us/sql/odbc/reference/develop-app/hard-coded-sql-statements . Existe algum benefício em preparar uma instrução SQL?

w0051977
fonte
31
"Usar Linq" e "Usar procedimentos armazenados" não são motivos; são apenas sugestões. Aguarde duas semanas e pergunte a ele por razões.
Robert Harvey
47
A rede Stack Exchange usa um micro-ORM chamado Dapper. Eu acho que é razoável dizer que a grande maioria do código Dapper é "SQL codificado" (mais ou menos). Portanto, se é uma prática ruim, é uma prática ruim adotada por um dos aplicativos da Web mais importantes do planeta.
Robert Harvey
16
Para responder sua pergunta sobre repositórios, o SQL codificado permanentemente ainda é codificado, não importa onde você o coloque. A diferença é que o repositório oferece um lugar para encapsular o SQL codificado. É uma camada de abstração que oculta os detalhes do SQL do restante do programa.
Robert Harvey
26
Não, o SQL no código não é um antipadrão. Mas isso é uma enorme quantidade de código de placa de caldeira para uma simples consulta SQL.
GrandmasterB
45
Há uma diferença entre 'no código fonte' e 'espalhados por todo o código-fonte'
tofro

Respostas:

112

Você excluiu a parte crucial da simplicidade. O repositório é a camada de abstração para persistência. Separamos a persistência em sua própria camada, para que possamos alterar a tecnologia de persistência mais facilmente quando necessário. Portanto, ter o SQL fora da camada de persistência frustra completamente o esforço de ter uma camada de persistência separada.

Como resultado: o SQL é bom dentro da camada de persistência específica para uma tecnologia SQL (por exemplo, SQL é bom em um, SQLCustomerRepositorymas não em um MongoCustomerRepository). Fora da camada de persistência, o SQL interrompe sua abstração e, portanto, é considerada uma prática muito ruim (por mim).

Quanto a ferramentas como LINQ ou JPQL: elas podem apenas abstrair os sabores do SQL por aí. Ter consultas LINQ-Code ou JPQL fora de um repositório interrompe a abstração de persistência tanto quanto o SQL bruto.


Outra grande vantagem de uma camada de persistência separada é que ela permite unittest seu código de lógica de negócios sem a necessidade de configurar um servidor de banco de dados.

Você obtém testes rápidos com baixo perfil de memória e resultados reproduzíveis em todas as plataformas suportadas pelo seu idioma.

Em uma arquitetura MVC + Service, é uma tarefa simples de zombar da instância do repositório, criando alguns dados simulados na memória e definindo que o repositório retorne esses dados simulados quando um determinado getter for chamado. Você pode definir os dados de teste por unittest e não se preocupar com a limpeza do banco de dados posteriormente.

O teste de gravações no banco de dados é tão simples: verifique se os métodos de atualização relevantes na camada de persistência foram chamados e afirme que as entidades estavam no estado correto quando isso aconteceu.

marstato
fonte
80
A idéia de que "podemos mudar a tecnologia da persistência" é irreal e desnecessária na grande maioria dos projetos do mundo real. Um banco de dados SQL / relacional é um animal completamente diferente dos bancos de dados NoSQL, como o MongoDB. Veja este artigo detalhado sobre o assunto. No máximo, um projeto que começou a usar o NoSQL acabaria percebendo seu erro e depois mudaria para um RDBMS. Na minha experiência, permitir o uso direto de uma linguagem de consulta orientada a objetos (como JPA-QL) do código comercial fornece os melhores resultados.
Rogério
6
E se houver muito pouca chance de a camada de persistência ser alterada? E se não houver um mapeamento individual ao alterar a camada de persistência? Qual é a vantagem do nível extra de abstração?
pllee
19
@ Rogério A migração de uma loja NoSQL para um RDBMS, ou vice-versa, é como você diz, provavelmente não realista (assumindo que a escolha da tecnologia era apropriada para começar). No entanto, participei de vários projetos do mundo real onde migramos de um RDBMS para outro; Nesse cenário, uma camada de persistência encapsulada é definitivamente uma vantagem.
David
6
"A idéia de que" podemos mudar a tecnologia de persistência "é irreal e desnecessária na grande maioria dos projetos do mundo real." Minha empresa já havia escrito código com base nessa suposição. Tivemos que refatorar toda a base de código para suportar não um, mas três sistemas de armazenamento / consulta totalmente diferentes e estamos considerando um terceiro e um quarto. Pior, mesmo quando tínhamos apenas um banco de dados, a falta de consolidação levou a todos os tipos de pecados de código.
NPSF3000 17/17/17
3
@ Rogério Você conhece a "grande maioria dos projetos do mundo real"? Na minha empresa, a maioria dos aplicativos pode funcionar com um dos muitos DBMS comuns, e isso é muito útil, pois nossos clientes têm requisitos não funcionais a esse respeito.
BartoszKP
55

Hoje, a maioria dos aplicativos de negócios padrão usa camadas diferentes com responsabilidades diferentes. No entanto, quais camadas você usa para seu aplicativo e qual camada possui qual responsabilidade é sua e de sua equipe. Antes de tomar uma decisão sobre se é certo ou errado colocar o SQL diretamente na função que você nos mostrou, precisa saber

  • qual camada do seu aplicativo tem qual responsabilidade

  • a partir de qual camada a função acima é de

Não existe uma solução "tamanho único" para isso. Em alguns aplicativos, os designers preferem usar uma estrutura ORM e deixar a estrutura gerar todo o SQL. Em alguns aplicativos, os designers preferem armazenar esse SQL exclusivamente em procedimentos armazenados. Para alguns aplicativos, há uma camada de persistência (ou repositório) escrita à mão onde o SQL vive e, para outros aplicativos, não há problema em definir exceções em determinadas circunstâncias ao colocar estritamente o SQL nessa camada de persistência.

Então o que você precisa pensar sobre: quais camadas você quer ou necessidade em sua aplicação particular, e como você deseja que as responsabilidades? Você escreveu "Eu normalmente teria uma camada de repositório" , mas quais são as responsabilidades exatas que você deseja ter dentro dessa camada e quais responsabilidades deseja colocar em outro lugar? Responda isso primeiro e, em seguida, você poderá responder sua pergunta sozinho.

Doc Brown
fonte
1
Eu editei a pergunta. Não tenho certeza se lança alguma luz.
w0051977 14/05
18
@ w0051977: o link que você postou não nos diz nada sobre a sua inscrição, certo? Parece que você está procurando por uma regra simples e simples, onde colocar SQL ou não, que se encaixe em todos os aplicativos. Não há nenhum. Essa é uma decisão de design que você deve tomar sobre seu aplicativo individual (provavelmente junto com sua equipe).
Doc Brown
7
+1. Em particular, eu observaria que não há diferença real entre SQL em um método e SQL em um procedimento armazenado, em termos de quão "codificado" é algo ou de reutilizável, de manutenção etc. Qualquer abstração que você possa fazer com um método procedimento pode ser feito com um método É claro que os procedimentos podem ser úteis, mas uma regra básica "não podemos ter SQL nos métodos, então vamos colocar tudo nos procedimentos" provavelmente produzirá muito cruft com pouco benefício.
1
@ user82096 Eu diria que, de um modo geral, a colocação de código de acesso db nos SPs foi prejudicial em quase todos os projetos que vi. (Pilha do MS) Além das degradações reais do desempenho (caches rígidos do plano de execução), a manutenção do sistema era mais difícil com a divisão da lógica e os SPs mantidos por um conjunto de pessoas diferente dos desenvolvedores de lógica do aplicativo. Especialmente no Azure, onde as alterações de código entre os Slots de Implantação são como segundos de trabalho, mas as migrações de esquema sem código primeiro / db-primeiro entre os slots são uma dor de cabeça operacional.
Sentinel
33

Marstato dá uma boa resposta, mas eu gostaria de adicionar mais alguns comentários.

O SQL na fonte NÃO é um anti-padrão, mas pode causar problemas. Lembro-me de quando você costumava colocar consultas SQL nas propriedades dos componentes soltos em todos os formulários. Isso tornou as coisas muito feias muito rápidas e você teve que pular os bastidores para localizar as consultas. Tornei-me um forte defensor da centralização do acesso ao banco de dados, tanto quanto possível, dentro das limitações dos idiomas com os quais estava trabalhando. Seu colega pode estar recebendo flashbacks desses dias sombrios.

Agora, alguns dos comentários estão falando sobre o bloqueio do fornecedor, como se isso fosse automaticamente uma coisa ruim. Não é. Se estou assinando uma verificação de seis dígitos a cada ano para usar o Oracle, você pode apostar que eu quero que qualquer aplicativo que acesse esse banco de dados esteja usando a sintaxe extra do Oracle adequadamentemas ao máximo. Não ficarei feliz se meu banco de dados brilhante estiver aleijado por codificadores que escrevem mal ANSI SQL baunilha quando houver uma "maneira Oracle" de escrever o SQL que não prejudique o banco de dados. Sim, a alteração dos bancos de dados será mais difícil, mas só vi isso em um grande site de clientes algumas vezes em mais de 20 anos e um desses casos foi transferido do DB2 -> Oracle porque o mainframe que hospedava o DB2 estava obsoleto e foi descomissionado . Sim, é um bloqueio do fornecedor, mas para clientes corporativos é realmente desejável pagar por um RDBMS com capacidade cara, como Oracle ou Microsoft SQL Server, e utilizá-lo em toda a extensão. Você tem um contrato de suporte como cobertor de conforto. Se estou pagando por um banco de dados com uma implementação rica de procedimento armazenado,

Isso leva ao próximo ponto: se você está escrevendo um aplicativo que acessa um banco de dados SQL, precisa aprender SQL , bem como a outra linguagem, e aprender, quero dizer otimização de consultas também; Ficarei zangado com você se você escrever código de geração SQL que libere o cache SQL com uma enxurrada de consultas quase idênticas, quando você poderia ter usado uma consulta inteligente e com parâmetros.

Sem desculpas, sem se esconder atrás de um muro do Hibernate. ORMs mal utilizados podem realmente prejudicar o desempenho dos aplicativos. Lembro-me de ter visto uma pergunta no Stack Overflow há alguns anos, seguindo as seguintes linhas:

No Hibernate, eu estou percorrendo 250.000 registros, verificando os valores de algumas propriedades e atualizando objetos que correspondem a determinadas condições. Está um pouco lento, o que posso fazer para acelerar?

Que tal "UPDATE table SET field1 = onde field2 é True e Field3> 100". Criar e descartar 250.000 objetos pode muito bem ser seu problema ...

ou seja, ignore o Hibernate quando não for apropriado usá-lo. Entenda o banco de dados.

Portanto, em resumo, a incorporação de SQL no código pode ser uma prática ruim, mas há coisas muito piores que você pode acabar tentando evitar a incorporação de SQL.

mcottle
fonte
5
Eu não afirmei que "bloqueio de fornecedor" é algo ruim. Eu afirmei que vem com trade-offs. No mercado de hoje com db como xaaS, contratos e licenças antigos para executar db auto-hospedado serão cada vez mais raros. Hoje, é muito mais fácil alterar o banco de dados do fornecedor A para B em comparação com o que era há 10 anos. Se você ler os comentários, não sou a favor de tirar proveito de qualquer recurso do armazenamento de dados. Portanto, é fácil colocar alguns detalhes específicos do fornecedor em algo (aplicativo) destinado a ser independente do fornecedor. Nós nos esforçamos para seguir o SOLiD até o final, bloqueando o código em um único armazenamento de dados. Não é grande coisa
Laiv
2
Esta é uma ótima resposta. Geralmente, a abordagem correta é aquela que leva à situação menos ruim se o pior código possível for escrito. O pior código ORM possível pode ser muito pior que o pior SQL incorporado possível.
Jwg 15/05
@Laiv bons pontos O PaaS é um fator de mudança no jogo - vou ter que pensar mais nas implicações.
Mcottle
3
@jwg eu estaria inclinado a dizer que o código acima da média ORM é pior do que abaixo SQL média incorporado :)
mcottle
@macottle Supondo que o SQL incorporado abaixo da média tenha sido escrito por pessoas acima da média daquele que escreve um ORM. O que eu duvido muito. Muitos desenvolvedores por aí não são capazes de escrever JOINs à esquerda sem verificar primeiro o SO.
Laiv
14

Sim, codificar seqüências de caracteres SQL no código do aplicativo geralmente é um anti-padrão.

Vamos tentar anular a tolerância que desenvolvemos após anos vendo isso no código de produção. Misturar linguagens completamente diferentes com sintaxe diferente no mesmo arquivo geralmente não é uma técnica de desenvolvimento desejável. Isso é diferente das linguagens de gabarito, como o Razor, projetadas para dar significado contextual a vários idiomas. Como Sava B. menciona em um comentário abaixo, o SQL em seu C # ou outra linguagem de aplicativo (Python, C ++ etc.) é uma string como qualquer outra e é semanticamente sem sentido. O mesmo se aplica ao misturar mais de um idioma na maioria dos casos, embora obviamente existam situações em que isso é aceitável, como montagem embutida em C, trechos pequenos e compreensíveis de CSS em HTML (observando que CSS foi projetado para ser misturado com HTML ), e outros.

Código Limpo de Robert C. Martin, pág.  288 (Robert C. Martin, sobre mistura de idiomas, Código Limpo , Capítulo 17, "Código Cheiros e Heurísticas", página 288)

Para esta resposta, focarei o SQL (conforme solicitado na pergunta). Os seguintes problemas podem ocorrer ao armazenar o SQL como um conjunto à la carte de seqüências de caracteres desassociadas:

  • É difícil localizar a lógica do banco de dados. O que você procura para encontrar todas as suas instruções SQL? Seqüências de caracteres com "SELECT", "UPDATE", "MERGE", etc.?
  • A refatoração de usos do mesmo SQL ou similar se torna difícil.
  • Adicionar suporte para outros bancos de dados é difícil. Como alguém conseguiria isso? Adicione instruções if..then para cada banco de dados e armazene todas as consultas como strings no método?
  • Os desenvolvedores leem uma declaração em outro idioma e se distraem com a mudança de foco do objetivo do método para os detalhes de implementação do método (como e de onde os dados são recuperados).
  • Embora as one-liners não sejam muito problemáticas, as seqüências SQL em linha começam a desmoronar à medida que as instruções se tornam mais complexas. O que você faz com uma declaração de linha 113? Coloque todas as 113 linhas no seu método?
  • Como o desenvolvedor move com eficiência as consultas entre o editor SQL (SSMS, SQL Developer etc.) e o código-fonte? O @prefixo do C # facilita isso, mas vi um monte de código que cita cada linha SQL e escapa às novas linhas. "SELECT col1, col2...colN"\ "FROM painfulExample"\ "WHERE maintainability IS NULL"\ "AND modification.effort > @necessary"\
  • Os caracteres de recuo usados ​​para alinhar o SQL com o código do aplicativo circundante são transmitidos pela rede a cada execução. Isso provavelmente é insignificante para aplicativos de pequena escala, mas pode aumentar conforme o uso do software aumenta.

ORMs completos (mapeadores objeto-relacionais como Entity Framework ou Hibernate) podem eliminar SQL aleatoriamente salpicados no código do aplicativo. Meu uso de SQL e arquivos de recursos é apenas um exemplo. ORMs, classes auxiliares etc. podem ajudar a atingir o objetivo de um código mais limpo.

Como Kevin disse em uma resposta anterior, o SQL no código pode ser aceitável em projetos pequenos, mas os projetos grandes começam como projetos pequenos, e a probabilidade de a maioria das equipes voltar e fazer o que é certo muitas vezes é inversamente proporcional ao tamanho do código.

Existem muitas maneiras simples de manter o SQL em um projeto. Um dos métodos que eu costumo usar é colocar cada instrução SQL em um arquivo de recurso do Visual Studio, geralmente chamado "sql". Um arquivo de texto, documento JSON ou outra fonte de dados pode ser razoável, dependendo de suas ferramentas. Em alguns casos, uma classe separada dedicada à instalação de seqüências SQL pode ser a melhor opção, mas pode ter alguns dos problemas descritos acima.

Exemplo SQL: Qual é a aparência mais 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 });
}

Torna-se

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

O SQL está sempre em um arquivo fácil de localizar ou em um conjunto agrupado de arquivos, cada um com um nome descritivo que descreve o que faz e não como faz, cada um com espaço para um comentário que não interromperá o fluxo do código do aplicativo :

SQL em um recurso

Este método simples executa uma consulta solitária. Na minha experiência, o benefício aumenta à medida que o uso da "língua estrangeira" se torna mais sofisticado. Meu uso de um arquivo de recurso é apenas um exemplo. Métodos diferentes podem ser mais apropriados, dependendo da linguagem (neste caso, SQL) e plataforma.

Este e outros métodos resolvem a lista acima da seguinte maneira:

  1. O código do banco de dados é fácil de localizar porque já está centralizado. Em projetos maiores, agrupe o like-SQL em arquivos separados, talvez em uma pasta chamada SQL.
  2. O suporte para um segundo, terceiro etc. bancos de dados é mais fácil. Faça uma interface (ou outra abstração de idioma) que retorne as instruções exclusivas de cada banco de dados. A implementação para cada banco de dados torna-se pouco mais do que declarações semelhantes a: return SqlResource.DoTheThing;Verdadeiro, essas implementações podem ignorar o recurso e conter o SQL em uma cadeia de caracteres, mas alguns problemas (nem todos) acima ainda apareceriam.
  3. A refatoração é simples - basta reutilizar o mesmo recurso. Você pode até usar a mesma entrada de recurso para diferentes sistemas DBMS na maioria das vezes com algumas instruções de formato. Eu faço isso frequentemente.
  4. O uso da linguagem secundária pode usar nomes descritivos, por exemplo, em sql.GetOrdersForAccountvez de mais obtusosSELECT ... FROM ... WHERE...
  5. As instruções SQL são convocadas com uma linha, independentemente do tamanho e da complexidade.
  6. O SQL pode ser copiado e colado entre ferramentas de banco de dados, como SSMS e SQL Developer, sem modificação ou cópia cuidadosa. Sem aspas. Sem barras invertidas à direita. No caso do editor de recursos do Visual Studio especificamente, um clique destaca a instrução SQL. CTRL + C e cole-o no editor SQL.

A criação do SQL em um recurso é rápida, portanto, há pouco impulso para misturar o uso de recursos com o SQL-in-code.

Independentemente do método escolhido, descobri que a mistura de linguagens geralmente reduz a qualidade do código. Espero que alguns problemas e soluções descritos aqui ajudem os desenvolvedores a eliminar esse cheiro de código, quando apropriado.

Charles Burns
fonte
13
Eu acho que isso se concentra demais em uma crítica ruim de ter SQL no código-fonte. Ter dois idiomas diferentes no mesmo arquivo não é necessariamente terrível. Depende de muitas coisas, como a relação entre elas e a forma como são apresentadas.
Jwg 15/05
9
"misturando linguagens completamente diferentes com sintaxe diferente no mesmo arquivo" Este é um argumento terrível. Também se aplicaria ao mecanismo de exibição Razor, e HTML, CSS e Javascript. Ame suas novelas gráficas!
22817 Ian Newson
4
Isso apenas empurra a complexidade para outro lugar. Sim, seu código original é mais limpo, às custas de adicionar um indireto para o qual o desenvolvedor agora precisa navegar para fins de manutenção. O verdadeiro motivo para você fazer isso é se cada instrução SQL for usada em vários locais do código. Dessa forma, não é realmente diferente de qualquer outra refatoração comum ("Método de extração").
Robert Harvey
3
Para ser mais claro, essa não é uma resposta para a pergunta "o que faço com minhas seqüências de caracteres SQL", mas é uma resposta para a pergunta "o que faço com qualquer coleção de seqüências de caracteres suficientemente complexas", uma pergunta que sua resposta aborda eloquentemente.
Robert Harvey
2
@RobertHarvey: Tenho certeza de que nossas experiências diferem, mas por mim mesmo achei a abstração em questão uma vitória na maioria dos casos. Certamente refinarei minhas trocas de abstração, como fiz há vários anos, e um dia posso colocar o SQL novamente em código. YMMV. :) Acho que os microsserviços podem ser ótimos e geralmente também separam os detalhes do SQL (se é que o SQL é usado) do código do aplicativo principal. Estou menos defendendo "use meu método específico: recursos" e mais defendendo "Geralmente não misture linguagens de óleo e água".
Charles Burns
13

Depende. Existem várias abordagens diferentes que podem funcionar:

  1. Modularize o SQL e isole-o em um conjunto separado de classes, funções ou qualquer unidade de abstração usada por seu paradigma e, em seguida, chame-o com a lógica do aplicativo.
  2. Mova todo o SQL complexo para as visualizações e, em seguida, execute apenas SQL muito simples na lógica do aplicativo, para que você não precise modularizar nada.
  3. Use uma biblioteca de mapeamento objeto-relacional.
  4. YAGNI, basta escrever SQL diretamente na lógica do aplicativo.

Como geralmente ocorre, se o seu projeto já escolheu uma dessas técnicas, você deve ser consistente com o restante do projeto.

(1) e (3) são relativamente bons em manter a independência entre a lógica do aplicativo e o banco de dados, no sentido de que o aplicativo continuará a compilar e passar nos testes básicos de fumaça se você substituir o banco de dados por um fornecedor diferente. No entanto, a maioria dos fornecedores não está totalmente em conformidade com o padrão SQL; portanto, a substituição de qualquer fornecedor por qualquer outro fornecedor provavelmente requer testes extensivos e busca de bugs, independentemente da técnica usada. Estou cético de que isso seja tão importante quanto as pessoas pensam ser. A alteração dos bancos de dados é basicamente o último recurso quando você não pode obter o banco de dados atual para atender às suas necessidades. Se isso acontecer, você provavelmente escolheu mal o banco de dados.

A escolha entre (1) e (3) é principalmente uma questão de quanto você gosta de ORMs. Na minha opinião, eles são usados ​​em excesso. Eles são uma representação ruim do modelo de dados relacionais, porque as linhas não têm identidade da maneira que os objetos têm identidade. É provável que você encontre pontos problemáticos em torno de restrições e uniões únicas e poderá ter dificuldade para expressar algumas consultas mais complicadas, dependendo do poder do ORM. Por outro lado, (1) provavelmente exigirá significativamente mais código do que um ORM.

(2) é raramente visto, na minha experiência. O problema é que muitas lojas proíbem os SWEs de modificar diretamente o esquema do banco de dados (porque "esse é o trabalho do DBA"). Isso não é necessariamente uma coisa ruim em si; as alterações de esquema têm um potencial significativo para quebrar coisas e podem precisar ser implementadas com cuidado. No entanto, para que (2) funcione, os SWEs devem pelo menos poder introduzir novas visões e modificar as consultas de suporte das visões existentes com pouca ou nenhuma burocracia. Se não for esse o caso no seu local de trabalho, (2) provavelmente não funcionará para você.

Por outro lado, se você conseguir fazer o (2) funcionar, é muito melhor que a maioria das outras soluções, pois mantém a lógica relacional no SQL, em vez do código do aplicativo. Diferentemente das linguagens de programação de uso geral, o SQL é projetado especificamente para o modelo de dados relacionais e, portanto, é melhor para expressar consultas e transformações complicadas de dados. As visualizações também podem ser portadas junto com o restante do esquema ao alterar os bancos de dados, mas elas tornarão essas mudanças mais complicadas.

Para leituras, os procedimentos armazenados são basicamente apenas uma versão de baixa qualidade de (2). Eu não os recomendo nessa capacidade, mas você ainda pode desejá-los para gravações, se seu banco de dados não suportar visualizações atualizáveis ou se precisar fazer algo mais complexo do que inserir ou atualizar uma única linha de cada vez (por exemplo, transações, leitura e gravação etc.). Você pode acoplar seu procedimento armazenado a uma exibição usando um gatilho (por exemplo,CREATE TRIGGER trigger_name INSTEAD OF INSERT ON view_name FOR EACH ROW EXECUTE PROCEDURE procedure_name;), mas as opiniões variam consideravelmente sobre se essa é realmente uma boa ideia. Os proponentes dirão que ele mantém o SQL que seu aplicativo executa da maneira mais simples possível. Os detratores dirão que esse é um nível inaceitável de "mágica" e que você deve apenas executar o procedimento diretamente do seu aplicativo. Eu diria que esta é uma idéia melhor se o seu procedimento armazenado parece ou age muito parecido com um INSERT, UPDATEou DELETE, e uma ideia pior se ele está fazendo outra coisa. Por fim, você terá que decidir por si mesmo qual estilo faz mais sentido.

(4) é a não solução. Pode valer a pena para projetos pequenos ou grandes que apenas interagem esporadicamente com o banco de dados. Mas para projetos com muito SQL, não é uma boa ideia, pois você pode ter duplicatas ou variações da mesma consulta espalhadas aleatoriamente em seu aplicativo, o que interfere na legibilidade e na refatoração.

Kevin
fonte
2, conforme escrito, pressupõe essencialmente um banco de dados somente leitura. Os procedimentos armazenados costumavam não ser incomuns para consultas modificadas.
Jpmc26
@ jpmc26: eu abro as gravações entre parênteses ... você teve uma recomendação específica para melhorar esta resposta?
21717 Kevin
Hum. Desculpas por comentar antes de completar a resposta e depois se distrair. Mas as visualizações atualizáveis ​​tornam bastante difícil rastrear onde uma tabela está sendo atualizada. A restrição de atualizações diretamente nas tabelas, independentemente do mecanismo, tem suas vantagens em termos de compreensão da lógica do código. Se você estiver restringindo a lógica ao banco de dados, não poderá impor isso sem os procedimentos armazenados. Eles também têm a vantagem de permitir várias operações com uma única chamada. Bottom line: Eu não acho que você deve ser tão duro com eles.
Jpmc26
@ jpmc26: Isso é justo.
Kevin
8

É considerado um anti-padrão escrever SQL no código-fonte?

Não necessariamente. Se você ler todos os comentários aqui, encontrará argumentos válidos para codificar instruções SQL no código-fonte.

O problema vem com o local em que você coloca as declarações . Se você colocar as instruções SQL em todo o projeto, em qualquer lugar, provavelmente estará ignorando alguns dos princípios do SOLID que normalmente nos esforçamos para seguir.

suponha que ele quis dizer; ou:

1) Use LINQ

ou

2) Use procedimentos armazenados para o SQL

Não podemos dizer o que ele quis dizer. No entanto, podemos adivinhar. Por exemplo, o primeiro que me vem à mente é o aprisionamento de fornecedores . As instruções SQL codificadas podem levar você a associar firmemente seu aplicativo ao mecanismo de banco de dados. Por exemplo, usando funções específicas do fornecedor que não são compatíveis com ANSI.

Isso não é necessariamente errado nem ruim. Estou apenas apontando para o fato.

Ignorar os princípios do SOLID e os bloqueios do fornecedor tem possíveis consequências adversas que você pode estar ignorando. É por isso que geralmente é bom sentar com a equipe e expor suas dúvidas.

Acho que o benefício dos procedimentos armazenados é que os desenvolvedores do SQL podem se envolver com o processo de desenvolvimento (escrevendo procedimentos armazenados, etc.)

Eu acho que não tem nada a ver com as vantagens dos procedimentos armazenados. Além disso, se o seu colega não gostar de SQL codificado, é provável que você mude os negócios para procedimentos armazenados também não gostará dele.

Editar: O link a seguir fala sobre instruções SQL codificadas:  https://docs.microsoft.com/en-us/sql/odbc/reference/develop-app/hard-coded-sql-statements . Existe algum benefício em preparar uma instrução SQL?

Sim. O post enumera as vantagens de declarações preparadas . É uma espécie de modelagem SQL. Mais seguro que a concatenação de strings. Mas o post não está incentivando você a seguir esse caminho nem confirmando que você está certo. Apenas explica como podemos usar o SQL codificado de maneira segura e eficiente.

Resumindo, tente perguntar ao seu colega primeiro. Envie um e-mail, ligue para ele, ... Se ele responde ou não, sente-se com a equipe e exponha suas dúvidas. Encontre a solução que melhor atenda às suas necessidades. Não faça suposições falsas com base no que você lê por aí.

Laiv
fonte
1
Obrigado. +1 para "Instruções de codificação SQL podem levar você a acoplar firmemente seu aplicativo ao mecanismo de banco de dados". Eu perambulo se ele estava se referindo ao SQL Server em vez de SQL, ou seja, usando SQLConnection em vez de dbConnection; SQLCommand em vez de dbCommand e SQLDataReader em vez de dbDataReader.
w0051977 14/05
Não. Eu estava me referindo ao uso de expressões não compatíveis com ANSI. Por exemplo: select GETDATE(), ....GETDATE () é a função de data do SqlServer. Em outras enginees, a função tem nome diferente, expressão diferente, ...
LAIV
14
"Bloqueio de fornecedor" ... Com que frequência as pessoas / empresas realmente migram o banco de dados do produto existente para outro RDBMS? Mesmo em projetos usando exclusivamente ORM, nunca vi isso acontecer: geralmente o software também é reescrito do zero, porque as necessidades mudaram no meio. Portanto, pensar sobre isso parece muito com "otimização prematura" para mim. Sim, é totalmente subjetivo.
Olivier Grégoire 15/05
1
Eu não me importo com que frequência acontece. Eu me importo Isso pode acontecer. Em projetos greenfields, o custo de implementação de um DAL extraído do banco de dados é quase 0. Uma possível migração não é. Os argumentos contra o ORM são bastante fracos. ORMs não são como 15 anos atrás, quando o Hibernate era bastante verde. O que eu vi são muitas configurações "por padrão" e muito mais designs de modelos de dados ruins. É como muitas outras ferramentas, muitas pessoas fazem o tutorial de "introdução" e não se importam com o que acontece oculto. A ferramenta não é o problema. O problema é quem não sabe como e quando usá-lo.
Laiv 15/05
Por outro lado, o fornecedor bloqueado não é necessariamente ruim. Se me perguntassem, diria que não gosto . No entanto, eu já estou bloqueado para Spring, Tomcat ou JBoss ou Websphere (ainda pior). Aqueles que eu posso evitar, eu faço. Aqueles que eu não posso, simplesmente vivo com Ele. É questão de preferências.
Laiv
3

Eu acho que é uma prática ruim, sim. Outros apontaram as vantagens de manter todo o seu código de acesso a dados em sua própria camada. Você não precisa procurar por isso, é mais fácil otimizar e testar ... Mas mesmo nessa camada, você tem algumas opções: usar um ORM, usar sprocs ou incorporar consultas SQL como strings. Eu diria que as strings de consulta SQL são de longe a pior opção.

Com um ORM, o desenvolvimento se torna muito mais fácil e menos propenso a erros. Com o EF, você define seu esquema apenas criando suas classes de modelo (você precisaria criar essas classes de qualquer maneira). Consultar com LINQ é fácil - você costuma sair com duas linhas de c # onde seria necessário escrever e manter um sproc. Na IMO, isso tem uma enorme vantagem quando se trata de produtividade e manutenção - menos código, menos problemas. Mas há uma sobrecarga de desempenho, mesmo se você souber o que está fazendo.

Sprocs (ou funções) são a outra opção. Aqui, você escreve suas consultas SQL manualmente. Mas pelo menos você tem alguma garantia de que eles estão corretos. Se você estiver trabalhando no .NET, o Visual Studio lançará erros de compilador se o SQL for inválido. Isso é ótimo. Se você alterar ou remover alguma coluna e algumas de suas consultas se tornarem inválidas, é provável que você descubra isso durante o tempo de compilação. Também é muito mais fácil manter sprocs em seus próprios arquivos - você provavelmente terá destaque de sintaxe, preenchimento automático ... etc.

Se suas consultas forem armazenadas como sprocs, você também poderá alterá-las sem recompilar e reimplementar o aplicativo. Se você perceber que algo no seu SQL está quebrado, um DBA pode corrigi-lo sem precisar ter acesso ao código do aplicativo. Em geral, se suas consultas estão em literais de string, você não pode fazer o trabalho deles com a mesma facilidade.

As consultas SQL como literais de strings também tornarão seu código c # de acesso a dados menos legível.

Como regra geral, constantes mágicas são ruins. Isso inclui literais de string.

Sava B.
fonte
Permitir que os DBAs alterem seu SQL sem atualizar o aplicativo está efetivamente dividindo seu aplicativo em duas entidades desenvolvidas e implementadas separadamente. Agora você precisa gerenciar o contrato de interface entre eles, sincronizar o lançamento de alterações interdependentes etc. Isso pode ser uma coisa boa ou ruim, mas é muito mais do que "colocar todo o seu SQL em um só lugar", é muito decisão arquitetônica.
IMSoP 20/09
2

Não é um antipadrão (esta resposta está perigosamente próxima da opinião). Mas a formatação do código é importante, e a string SQL deve ser formatada para que fique claramente separada do código que a utiliza. Por exemplo

    string query = 
    @"SELECT foo, bar
      FROM table
      WHERE id = @tn";
rleir
fonte
7
O problema mais flagrante com isso é a questão de onde esse valor 42 vem. Você está concatenando esse valor na string? Se sim, de onde veio? Como foi eliminado para mitigar ataques de injeção de SQL? Por que este exemplo não mostra uma consulta parametrizada?
22417 Craig
Eu só queria mostrar a formatação. Desculpe, esqueci de parametrizar. Agora fixado após referindo-se msdn.microsoft.com/library/bb738521(v=vs.100).aspx
rleir
+1 por importância da formatação, embora eu afirme que as strings SQL são um cheiro de código, independentemente da formatação.
Charles Burns
1
Você insiste no uso de um ORM? Quando um ORM não está em uso, para um projeto menor, eu uso uma classe 'shim' contendo SQL incorporado.
Rleir 21/05
Definitivamente, as seqüências SQL não são um cheiro de código. Sem um ORM, como gerente e arquiteto, eu os incentivaria pelos SPs por motivos de manutenção e, às vezes, de desempenho.
Sentinel
1

Não posso lhe dizer o que seu colega quis dizer. Mas eu posso responder isso:

Existe algum benefício em preparar uma instrução SQL?

SIM . O uso de instruções preparadas com variáveis ​​associadas é a defesa recomendada contra a injeção de SQL , que continua sendo o maior risco de segurança para aplicativos da web há mais de uma década . Esse ataque é tão comum que foi apresentado nos quadrinhos da web quase dez anos atrás, e é hora de defender defesas eficazes.

... e a defesa mais eficaz contra concatenação incorreta de cadeias de caracteres de consulta não representa consultas como cadeias de caracteres em primeiro lugar, mas para usar uma API de consulta de tipo seguro para criar consultas. Por exemplo, aqui está como eu escreveria sua consulta em Java com QueryDSL:

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

Como você pode ver, não há uma única string literal aqui, tornando impossível a injeção de SQL. Além disso, eu tive o preenchimento de código ao escrever isso, e meu IDE pode refatorá-lo, caso eu escolha renomear a coluna id. Além disso, posso encontrar facilmente todas as consultas da tabela de pessoas perguntando ao meu IDE onde a personvariável é acessada. Ah, e você notou que é um pouco mais curto e fácil aos olhos do que o seu código?

Só posso supor que algo assim também esteja disponível para C #. Por exemplo, ouço grandes coisas sobre o LINQ.

Em resumo, representar consultas como seqüências de caracteres SQL torna difícil para o IDE ajudar significativamente na gravação e refatoração de consultas, adia a detecção de erros de sintaxe e tipo do tempo de compilação para o tempo de execução e é uma causa contribuinte para as vulnerabilidades de injeção SQL [1]. Portanto, sim, existem razões válidas pelas quais alguém pode não querer seqüências de caracteres SQL no código-fonte.

[1]: Sim, o uso correto de instruções preparadas também evita a injeção de SQL. Mas essa outra resposta neste segmento ficou vulnerável a uma injeção de SQL até que um comentarista apontou isso não inspira confiança nos programadores juniores que os usassem corretamente sempre que necessário ...

Meriton
fonte
Para ser claro: o uso de instruções preparadas não impede a injeção de SQL. Usar instruções preparadas com variáveis ​​ligadas sim. É possível usar instruções preparadas criadas a partir de dados não confiáveis.
Andy Lester
1

Muitas ótimas respostas aqui. Além do que já foi dito, gostaria de acrescentar mais uma coisa.

Não considero o uso de SQL embutido um anti-padrão. No entanto, o que considero um antipadrão é todo programador fazendo suas próprias coisas. Você precisa decidir em equipe um padrão comum. Se todo mundo estiver usando o linq, use linq; se todo mundo estiver usando visualizações e procedimentos armazenados, faça isso.

Mas mantenha sempre a mente aberta e não caia no dogma. Se sua loja é uma loja "linq", você a usa. Exceto por aquele lugar em que o linq absolutamente não funciona. (Mas você não deve 99,9% do tempo.)

Então, o que eu faria é pesquisar o código na base de código e verificar como seus colegas trabalham.

Pieter B
fonte
+1 bom ponto. A capacidade de suporte é mais importante do que a maioria das outras considerações; portanto, não seja muito inteligente :) Dito isso, se você é uma loja de ORM, não esqueça que há casos em que o ORM não é a resposta e você precisa escrever SQL direto. Não otimize prematuramente, mas em qualquer aplicativo não trivial haverá momentos em que você terá que seguir esse caminho.
Mcottle
0

O código se parece com a camada de interação com o banco de dados que está entre o banco de dados e o restante do código. Se realmente for, isso não é anti-padrão.

Esse método é a parte da camada, mesmo que o OP pense de maneira diferente (acesso simples ao banco de dados, sem misturar com mais nada). Talvez seja apenas mal colocado dentro do código. Este método pertence à classe separada, "camada de interface do banco de dados". Seria antipadrão colocá-lo dentro da classe comum ao lado dos métodos que implementam atividades que não são de banco de dados.

O anti-padrão seria chamar SQL de algum outro local aleatório de código. Você precisa definir uma camada entre o banco de dados e o restante do código, mas não é necessário usar absolutamente alguma ferramenta popular que possa ajudá-lo lá.

h22
fonte
0

Na minha opinião, não é um anti-padrão, mas você se encontrará lidando com o espaçamento da formatação de string, especialmente para grandes consultas que não cabem em uma linha.

outro profissional é que fica muito mais difícil lidar com consultas dinâmicas que devem ser criadas de acordo com certas condições.

var query = "select * from accounts";

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

Eu sugiro usar um construtor de consultas sql

amd
fonte
Pode ser que você esteja usando apenas taquigrafia, mas é uma péssima idéia usar cegamente "SELECT *", pois você trará de volta os campos que não precisa (largura de banda !!) desce uma ladeira muito escorregadia até uma cláusula condicional "WHERE" e é uma estrada que leva diretamente ao inferno da performance.
Mcottle
0

Para muitas organizações modernas com pipelines de implantação rápida em que as alterações de código podem ser compiladas, testadas e colocadas em produção em minutos, faz todo o sentido incorporar instruções SQL ao código do aplicativo. Isso não é necessariamente um anti-padrão, dependendo de como você o faz.

Hoje, um caso válido para o uso de procedimentos armazenados está em organizações nas quais há muito processo em torno das implantações de produção que podem envolver semanas de ciclos de controle de qualidade etc. Nesse cenário, a equipe do DBA pode resolver problemas de desempenho que surgem somente após a implantação inicial da produção, otimizando as consultas nos procedimentos armazenados.

A menos que você esteja nessa situação, recomendo que você incorpore seu SQL ao código do aplicativo. Leia outras respostas neste tópico para obter conselhos sobre a melhor maneira de fazê-lo.


Texto original abaixo para contexto

A principal vantagem dos procedimentos armazenados é que você pode otimizar o desempenho do banco de dados sem recompilar e reimplementar seu aplicativo.

Em muitas organizações, o código só pode ser enviado para produção após um ciclo de controle de qualidade, etc., que pode levar dias ou semanas. Se o seu sistema desenvolver um problema de desempenho do banco de dados devido a alterações nos padrões de uso ou aumentos nos tamanhos das tabelas, etc., pode ser bastante difícil rastrear o problema com consultas codificadas, enquanto o banco de dados terá ferramentas / relatórios, etc., que identificarão procedimentos armazenados com problemas . Com os procedimentos armazenados, as soluções podem ser testadas / comparadas na produção e o problema pode ser corrigido rapidamente, sem a necessidade de enviar uma nova versão do software aplicativo.

Se esse problema não for importante no sistema, é uma decisão perfeitamente válida para codificar instruções SQL no aplicativo.

bikeman868
fonte
Errado ...........
Sentinel
Você acha que seu comentário é útil para alguém?
precisa saber é o seguinte
Isso é simplesmente incorreto. Digamos que temos um projeto de API da Web2 com EF como acesso a dados e Azure Sql como armazenamento. Não, vamos nos livrar do EF e usar o SQL artesanal. O esquema db é armazenado em um projeto SSDT SQL Server. A migração do esquema db envolve garantir que a base de código seja sincronizada com o esquema db e totalmente testada, antes de enviar a alteração para um teste. Uma alteração de código envolve enviar para um slot de implantação do Serviço de Aplicativo, que leva 2 segundos. Além disso, o SQL artesanal possui problemas de desempenho em relação aos procedimentos armazenados, de um modo geral, apesar da 'sabedoria' do contrário.
Sentinel
No seu caso, a implantação pode levar segundos, mas esse não é o caso para muitas pessoas. Eu não disse que os procedimentos armazenados são melhores, apenas que eles têm algumas vantagens em algumas circunstâncias. Minha afirmação final é que, se essas circunstâncias não se aplicarem, colocar o SQL no aplicativo é muito válido. Como isso contradiz o que você está dizendo?
bikeman868
Procedimentos armazenados são SQL criados à mão ???
precisa saber é o seguinte