21 10 2015
Parâmetros do ADO.NET: por favor, pare de concatenar strings nas suas sentenças SQL!
Eu tenho certeza que, em alguma parte do código das suas aplicações, você já precisou realizar alguma consulta no seu banco de dados, não é mesmo? Isso é uma tarefa tão comum que acabamos nem nos dando conta se estamos fazendo da forma correta.
Ao realizarmos consultas utilizando as classes base do ADO.NET (DbConnection, DbCommand, etc), é muito fácil expormos a nossa aplicação a SQL injection. Fazemos isso ao concatenar strings quando precisamos passar algum valor externo para as nossas consultas. Hoje você aprenderá o jeito certo de se passar valores a comandos do ADO.NET: utilizando parâmetros!
O cenário de exemplo
Para entendermos exatamente o que estou querendo dizer, suponha que temos uma tabela muito simples no nosso banco de dados, chamada “ExemploTabela“:
CREATE TABLE ExemploTabela ( ID INT IDENTITY(1,1) NOT NULL, Descricao VARCHAR(50) NOT NULL, CONSTRAINT PK_ExemploTabela PRIMARY KEY CLUSTERED (ID ASC) ON [PRIMARY] ) ON [PRIMARY]
INSERT INTO ExemploTabela (Descricao) VALUES ('André Alves de Lima'); INSERT INTO ExemploTabela (Descricao) VALUES ('Fulano de Tal'); INSERT INTO ExemploTabela (Descricao) VALUES ('Ciclano Beltrano');
Em uma aplicação do tipo “Windows Forms Application“, vamos ajustar um formulário para que ele fique parecido com a imagem abaixo:
Como você pode ver, temos um TextBox (pesquisaTextBox), um botão (pesquisarButton) e um DataGridView (pesquisaDataGridView). A ideia é que, no clique no botão, vamos fazer uma pesquisa na tabela “ExemploTabela” utilizando o conteúdo do TextBox.
O jeito errado: concatenando strings
Antes de mostrarmos o jeito certo de se fazer consultas desse tipo, vamos ver como 90% das pessoas fazem: do jeito errado. Não me canso de ver pessoas nos fóruns da MSDN postando código concatenando strings nas consultas! Isso abre o seu sistema para uma infinidade de problemas, inclusive SQL injection, como eu mencionei no início desse artigo.
Enfim, o jeito errado seria algo como isto:
private void pesquisarButton_Click(object sender, EventArgs e) { using (var conn = new System.Data.SqlClient.SqlConnection(@"Server=seuserver;Database=seubanco;User Id=seuuser;Password=suasenha;")) { conn.Open(); using (var comm = conn.CreateCommand()) { comm.CommandText = "SELECT * FROM ExemploTabela WHERE Descricao LIKE '%" + pesquisaTextBox.Text + "%'"; var dataTable = new DataTable(); using (var reader = comm.ExecuteReader()) { dataTable.Load(reader); } pesquisaDataGridView.DataSource = dataTable; } } }
Esse código aparentemente funciona:
Mas, quer ver alguns dos problemas que ele pode trazer? Imagine que o usuário queira pesquisar algum nome que tenha apóstrofe no meio. Por exemplo: “O’neal”. Qual o resultado? Exception!
Mas, isso não é o pior! Está preparado para ficar chocado? Suponha que algum usuário mal-intencionado saiba que você tem essa brecha no seu sistema. Sabe o que esse usuário mal-intencionado consegue fazer? Ter acesso completo ao seu banco de dados, inclusive deletar tabelas se ele quiser!
Veja só. Imagine que você tenha uma outra tabela no seu banco de dados, chamada “OutraTabela“. Se o usuário mal-intencionado utilizar a seguinte string de busca na sua aplicação: “fula’; DROP TABLE OutraTabela;–“, ele vai conseguir deletar essa outra tabela sem muito esforço! Com o ponto e vírgula ele finaliza o comando anterior (o “SELECT” que faz a busca na tabela “ExemploTabela“) e, em seguida, adiciona o comando de “DROP” para excluir a outra tabela. Entendeu a gravidade dessa situação?
O jeito certo: parâmetros do ADO.NET
Agora que você já viu o perigo de se concatenar strings em queries do ADO.NET, que tal aprender o jeito certo de passar valores para as suas sentenças? Desde a primeira versão, os comandos do ADO.NET suportam o que chamamos de parâmetros. Os parâmetros nada mais são que uma maneira de disponibilizarmos valores para uma sentença, de forma que os valores sejam passados para a query de forma segura.
Por exemplo, se o parâmetro é do tipo string, não precisamos nos preocupar com a questão da apóstrofe (nem aspas simples), uma vez que o ADO.NET vai formatar o valor corretamente de forma que o comando seja executado sem problemas. Outro exemplo é a questão de datas. Dependendo do idioma do SQL Server, você precisa passar o formato de data de maneiras diferentes, senão a sentença não é executada corretamente. Ao utilizar os parâmetros do ADO.NET, você não precisa se preocupar mais com formato de data. O ADO.NET se encarrega de tudo.
E como é que fazemos para utilizar os parâmetros nas nossas sentenças com o ADO.NET? Simples, veja só:
private void pesquisarButton_Click(object sender, EventArgs e) { using (var conn = new System.Data.SqlClient.SqlConnection(@"Server=alves01dbserver\sql2012;Database=ExemploDB;User Id=sa;Password=savcor;")) { conn.Open(); using (var comm = conn.CreateCommand()) { comm.CommandText = "SELECT * FROM ExemploTabela WHERE Descricao LIKE @Descricao"; comm.Parameters.AddWithValue("@Descricao", string.Format("%{0}%", pesquisaTextBox.Text)); var dataTable = new DataTable(); using (var reader = comm.ExecuteReader()) { dataTable.Load(reader); } pesquisaDataGridView.DataSource = dataTable; } } }
Como você pode ver, ao invés de concatenar o valor no meio da query, você deve dar um nome para o parâmetro (no exemplo acima utilizei “@Descricao“). Depois, é só utilizar a propriedade Parameters do comando e chamar o método “AddWithValue“, passando o nome que você definiu para o parâmetro e o valor desejado.
Um único detalhe: a sintaxe para a definição do parâmetro no meio da sentença varia de provider para provider. O exemplo acima funciona para o provider do SQL Server (System.Data.SqlClient). O provider OleDb, por exemplo, não aceita parâmetros nomeados. Ao invés de definir um nome para o parâmetro, você precisa colocar “?” (ponto de interrogação) no lugar onde o valor deve ser colocado. Veja como ficaria o mesmo exemplo caso estivéssemos trabalhando com um banco de dados do Microsoft Access:
private void pesquisarButton_Click(object sender, EventArgs e) { using (var conn = new System.Data.OleDb.OleDbConnection(@"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\myFolder\myAccessFile.accdb;Persist Security Info=False;")) { conn.Open(); using (var comm = conn.CreateCommand()) { comm.CommandText = "SELECT * FROM ExemploTabela WHERE Descricao LIKE ?"; comm.Parameters.AddWithValue("Descricao", string.Format("%{0}%", pesquisaTextBox.Text)); var dataTable = new DataTable(); using (var reader = comm.ExecuteReader()) { dataTable.Load(reader); } pesquisaDataGridView.DataSource = dataTable; } } }
Para encontrar a referência de outros providers, busque no Google por “xxxCommand parameter” (por exemplo, MySqlCommand parameter ou NpgsqlCommand parameter).
EDIT: Depois de publicar este artigo, recebi um comentário do leitor Samuel Cavalcante, dizendo que ele consegue utilizar parâmetros nomeados com o Microsoft Access e provider OleDb:
Ele até preparou um exemplo, que você pode baixar aqui. Realmente o Microsoft Access suporta que você defina nomes para os parâmetros (ao invés de utilizar ponto de interrogação). Porém, no final das contas, o provider utiliza os parâmetros na ordem que eles forem especificados no comando (ou seja, não adianta de nada o nome do parâmetro, pois, o provider não o respeita).
Concluindo
SQL injection é um problema muito grave, mas, infelizmente, deixado de lado pela maioria dos programadores. Não seja um desses programadores.
Nesse artigo você aprendeu o porquê você não deve concatenar strings nas suas sentenças SQL e como fazer para passar valores sem ficar concatenando strings, utilizando a funcionalidade de parâmetros do ADO.NET.
E você, já realizou consultas da maneira errada? Eu já. Mas, não tem problema. O que importa é que você aprendeu agora a maneira correta de passar valores para as suas sentenças. Só me prometa que você nunca mais vai concatenar valores nas suas queries a partir de hoje, OK?
Antes de me despedir, convido você a inscrever-se na minha newsletter. Ao fazer isso, você receberá um e-mail toda semana sobre o artigo publicado, ficará sabendo em primeira mão sobre o artigo da próxima semana e receberá também dicas “bônus” que eu só compartilho por e-mail. Além disso, você já deve ter percebido que eu recebo muitas sugestões de temas e eu costumo dar prioridade às sugestões vindas de inscritos da minha newsletter. Inscreva-se utilizando o formulário logo abaixo.
Até a próxima!
André Lima
Periscope replay: Qual é melhor – Crystal Reports ou Report Viewer? Periscope replay: SQL Server, MySQL ou PostgreSQL?
Ótimo post! Muito claro e bem explicado! Parabéns xD
Olá Eduardo, obrigado pelo comentário! Fico feliz que você tenha gostado! Ah, e obrigado por responder o meu e-mail também! Amanhã devo respondê-lo..
Abraço!
André Lima
Por nada xD
Belo artigo André.
Já uso parâmetros há um bom tempo, porém sempre tive problemas quando possuo IN na minha consulta SQL. Já tentei de algumas formas sem sucesso, e na correria do dia a dia acabando concatenando os valores para o IN.
Você sabe me dizer como funcionaria o parâmetro em uma cláusula IN ?
Olá Fábio, obrigado pelo comentário!
As cláusulas IN realmente são chatas de lidar quando trabalhamos com parâmetro.. Eu tenho um jeito de resolver, inclusive já até escrevi sobre isso aqui no site, veja só:
Utilizando parâmetros do ADO.NET em cláusulas IN
Dê uma olhada e depois me diga o que achou..
Abraço!
André Lima
Confesso que estou mudando programas depois do seu artigo. Obrigado
Olá Antonio, obrigado pelo comentário!
Fico feliz que o artigo esteja ajudando nos seus projetos!
Abraco!
André Lima
Belo post, como sempre!
Olá Tony! Muito obrigado pelo comentário! Fico feliz que você tenha gostado.. :)
Abraço!
André Lima
Olá André,
Tenho uma dúvida quanto a este assunto. Eu tenho uma tela de pesquisas, parecida com essa que você colocou, mas além de um textbox, ela tem outros campos. Dois textbox (busca1 e busca 2) e outros campos para filtros ( fabricante, grupo, subgrupo e situação ) Se eu informar algo em busca1, ele faz a pesquisa não apenas no nome do meu cliente, mas faz também a busca no código, referencia, codigo ean e outros campos. O mesmo eu faço para o busca2. Assim eu posso colocar no busca1 parte do que eu quero pesquisa e no busca2 a outra parte da pesquisa. Eu vinha fazendo isso da forma errada como você disse, porém eu não consegui colocar o mesmo parametro para campos diferentes. Você pode me orientar?
Olá Sames, obrigado pelo comentário!
Claro que eu posso te ajudar.. Qual banco de dados você está utilizando? Basicamente é para funcionar normalmente se você usar o mesmo nome do parâmetro em vários lugares da query.. Facilitaria bastante se você pudesse compartilhar o trecho de código onde você está tentando implementar essa funcionalidade.. Poste aqui ou mande no meu e-mail..
Abraço!
André Lima
André,
Muito bom o post!!! Lembro como hoje, tem uns 6 meses, estava tirando dúvidas com um dos meus coordenadores como seria a melhor maneira de pegar os dados do meu DATETIMEPICKER para utilizar na minha query.
Ele me passou exatamente o modo correto e ainda escutei ele falar pra mim: “olhe o jeito que vc está fazendo (ERRADO), no todo não está, porém passando por parâmetro trás mais segurança para aplicação”
Agora puder ver exemplos práticos.
;)
Fala Nelson! Obrigado pelo comentário!
Fico feliz que você tenha gostado dos exemplos..
Abraço!
André Lima
[…] Pois bem, nós não passamos! Basta utilizarmos a funcionalidade de parâmetros do ADO.NET, passando a representação binária do arquivo em um parâmetro do comando. Com isso o ADO.NET vai se virar para mandar o arquivo para o banco de dados, sem que tenhamos que ficar fazendo malabarismos com a sentença SQL (para mais informações sobre parâmetros no ADO.NET, confira este artigo). […]
[…] de parâmetros do ADO.NET, mas sim concatena os valores direto na sentença SQL, pare agora mesmo e leia este outro artigo! Sério, é super […]
André,
Muito bom esse exemplo e a explicação melhor ainda, Parabéns!!!
Abraço!
Pablo Sousa
Olá Pablo!
Muito obrigado, fico feliz que tenha gostado.. :)
Abraço!
André Lima