C# - Trabalhando com dados binários no SQL Server (DataReader)
Neste artigo eu vou mostrar como podemos trabalhar com dados binários no SQL Server usando um DataReader com a linguagem C#.
O comportamento padrão do DataReader é obter os dados a partir da fonte linha por linha. Quando vamos trabalhar com dados binários(BLOB) usando o objeto DataReader o tratamento deve ser um pouco diferente pois eles podem conter um grande volume de dados que não podem estar contidos em uma única linha.
O método Command.ExecuteReader possui uma sobrecarga que usa um argumento CommandBehavior para modificar este comportamento padrão do DataReader.
Você pode passar um CommandBehavior.SequentialAcces para o método ExecuteReader para modificar o comportamento padrão do DataReader de forma que ao invés de carregar linhas de dados ele irá carregar os dados de forma sequencial conforme forem recebidos. Este é o tratamento ideal quando formos usar dados do tipo binário como os BLOB.
Usando SequentialAccess
A utilização do SequentialAccess permite que o DataReader carregue os dados como um fluxo de bytes (stream).
Ao definir a utilização do SequentialAccess é importante notar a sequência na qual você irá acessar os campos que forem retornados.
O comportamento padrão do DataReader permite que você acesse os campos retornados em qualquer ordem até que a próxima linha seja lida. Quando você for usar o SequentialAccess você precisa acessar os diferentes campos retornados pelo DataReader na ordem correta.
Ao usar um
DataReader,
se você for acessar os dados de forma sequencial (1o
campo, 2o campo, etc...) , poderá aumentar o desempenho
usando um commandBeheavior.SequentialAccess quando for
utilizar o ExecuteReader. Fazendo assim o DataReader é
avisado de que o acesso é sequencial e isso otimiza o
acesso. Os valores de CommandBehavior são usados pelo método ExecuteReader da interface IDbCommand e qualquer classe derivada dela. Exemplo de uso:
|
Assim se a sua consulta retorna 3 colunas e a terceira é um campo do tipo BLOB você deve retornar os valores do primeiro e do segundo campos antes de acessar os dados binários da terceira coluna. De outra forma os valores dos campos se tornam indisponíveis. Este comportamento é devido a alteração que o SequentialAcess realizou no DataReader para retornar os dados na sequência.
Quando acessar os dados em um campo do tipo BLOB você deve usar os tipos de acessos GetBytes ou GetChars que preenchem um array com dados.
Você até pode usar um GetString para dados do tipo caractere porém para conservar os recursos do sistema você não pode carregar um valor BLOB em um variável string. Você pode definir um buffer de dados com um determinado tamanho a ser retornado e iniciar a leitura partir do primeiro byte ou caractere a ser lido. GetBytes e GetChars retornam um valor do tipo long que representa o número de bytes ou caracteres retornados.Você também pode definir um índice no array indicando a posição a ordem dos dados a serem lidos.
Escrevendo BLOBS
Para escrever dados binários para o banco de dados você deve usar o comando SQL apropriado INSERT ou UPDATE e passar o valor do BLOB com um parâmetro de entrada. Se o seu BLOB esta armazenado como um texto(documento) você pode passar o BLOB como um parâmetro String. Se o BLOB esta armazenado no formato binário como uma imagem você pode passar um array de bytes como um parâmetro binário.
Abaixo temos um esquema simplificado do fluxo de atividades realizado para ler/gravar dados binário do tipo Image;
Vejamos a seguir um exemplo
que inclui informação sobre funcionários na tabela Funcionarios do banco de dados
que iremos criar. (Baseada na tabela Employee do banco de dados
Northwind) A estrutura da tabela é data a seguir:(No menu Data Selecione Add New Data Source) Uma foto do funcionário é lida a partir do arquivo e incluída no campo Foto na tabela que é um campo do tipo Image. O campo Foto é então lido usando GetBybtes. Note que o código do funcionário é acessado para a linha atual antes da foto pois os campos precisam ser acessados de forma sequencial e na ordem. |
Criando uma aplicação Windows Forms
Vamos criar uma aplicação Windows Forms usando o Visual C# Express Edition 2008 para incluir e acessar dados de uma tabela do SQL Server Express Edition.
Abra o Visual C# 2008 Express e crie uma nova aplicação Windows Application com o nome de usandoDadosBinarios;
A seguir inclua a partir da ToolBox os controles :
O leiaute do formulário deve ser parecido com o da imagem abaixo:
Antes de iniciar devemos declarar os namespaces :
using System.Data.SqlClient;
using
System.IO;
Devemos também definir as variáveis globais usadas no projeto; foto armazena o caminho da foto e conSQL a string de conexão obtida no construtor.
private string foto = null;O construtor do formulário esta definido da seguinte forma:
public
Form1() { InitializeComponent(); conSQL = new SqlConnection("Data Source=.\\SQLEXPRESS;AttachDbFilename='C:\\Dados\\Cadastro.mdf';Integrated Security=True;Connect Timeout=30;User Instance=True");atualizaGrid(); } |
Vejamos agora o código relacionado a cada evento Click dos botões de comando:
1- Botão Carrega Foto:
void TbnCarregaFotoClick(object sender, EventArgs e) { OpenFileDialog dlg = new OpenFileDialog(); dlg.Title = "Abrir Foto"; dlg.Filter = "JPG (*.jpg)|*.jpg" + "|All files (*.*)|*.*"; if (dlg.ShowDialog() == DialogResult.OK) { try { picFoto.Image = new Bitmap(dlg.OpenFile()); foto = dlg.FileName; } catch (Exception ex) { MessageBox.Show("Não foi possivel carregar a foto: " + ex.Message); } } dlg.Dispose(); } |
Neste código abrimos uma caixa de diálogo OpenFileDialog para selecionar o arquivo a ser exibido no controle PictureBox - picFoto, conforme a figura acima mostra. Essa rotina é usada para escolher uma foto do funcionário e em seguida informar seus dados para inclusão na base de dados.
2- Botão Incluir Dados
void BtnIncluirDadosClick(object sender, EventArgs e) {String nome = txtNome.Text; String endereco = txtEndereco.Text; String cargo = txtCargo.Text; DateTime admissao = DateTime.Now; IncluiFuncionario(nome,endereco,cargo,admissao,foto); barraStatus.Text = "Funcionário incluído no banco de dados"; } |
Este código obtém os valores informados no formulário e chama a rotina para incluir um novo funcionário na tabela;
3- Botão Selecionar
void BtnSelecionarClick(object sender, EventArgs e) { if (txtNome.Text.Equals("")) { MessageBox.Show("Informe o nome do funcionário."); return; } else { GetFuncionario(txtNome.Text); } barraStatus.Text = "Arquivo Salvo no sistema"; } |
Neste código vamos selecionar um funcionário através de seu nome usando a rotina getFuncionario();
Primeiro vamos incluir um botão com o texto Limpar para limpar os campos do formulário, desta forma o usuário poderá informar o nome do funcionário que deseja obter.
A rotina getFuncionario e dada a seguir:
// **** Le o BLOB a partir do banco de dados e salva no sistema de arquivos public void getFuncionario(string nome) { SqlCommand getFunci = new SqlCommand( "SELECT funciID, foto " + "FROM Funcionarios " + "WHERE nome = @Nome ", conSQL); getFunci.Parameters.Add("@Nome", SqlDbType.NVarChar, 50).Value = nome; FileStream fs; // Escreve o BLOB para o arquivo (*.bmp). BinaryWriter bw; // Define um Streams para o objeto int tamanhoBuffer = 100; // Tamanho do buffer do BLOB byte[] byteSaida = new byte[tamanhoBuffer]; // o buffer BLOB byte[] para ser preenchido com GetBytes. long retorno; // Os bytes retornados de GetBytes. long inicioIndice = 0; // A posicao inicial no BLOB de saida string funci_id = ""; // O codigo do funcionario em uso no arquivo // Abre a conexão e le os dados no DataReader. conSQL.Open(); SqlDataReader mReader = getFunci.ExecuteReader(CommandBehavior.SequentialAccess); while (mReader.Read()) { // Obtem o codigo que precisa existir antes de pegar o funcionario funci_id = mReader.GetInt32(0).ToString(); // Cria o arquivo para tratar a saida dos dados fs = new FileStream("funcionario" + funci_id + ".jpg", FileMode.OpenOrCreate, FileAccess.Write); bw = new BinaryWriter(fs); // Reseta o byte de inicio para o novo BLOB. inicioIndice = 0; // Le os bytes no byteSaida[] e retem o numero de bytes retornados retorno = mReader.GetBytes(1, inicioIndice, byteSaida, 0, tamanhoBuffer); // Continua lendo e escrevendo enquanto existir bytes ate completar o tamanho do buffer while (retorno == tamanhoBuffer) { bw.Write(byteSaida); bw.Flush(); //Reposiciona o inidice de inicio para o fim ultimo buffer e preenche o buffer inicioIndice += tamanhoBuffer; retorno = mReader.GetBytes(1, inicioIndice, byteSaida, 0, tamanhoBuffer); } // Escreve o restante do buffer bw.Write(byteSaida, 0, (int)retorno); bw.Flush(); // fecha o arquivo de saida bw.Close(); fs.Close(); } // fecha o datareader e a conexao mReader.Close(); conSQL.Close(); } |
Para incluir os dados de um funcionário primeiro você deve escolher a sua foto clicando no botão Carregar Foto e em seguida clicar em Incluir Dados:
// Le a imagem do arquivo de sistema e inclui no banco de dados public void IncluiFuncionario( string pnome, string pendereco, string pcargo, DateTime padmissao, string pcaminhoFoto) { // Le a imagem em um array de bytes do arquivo de sistemas byte[] fotoImagem = GetFoto(pcaminhoFoto); // MOnta a instrucao SQL SqlCommand incFunci = new SqlCommand( "INSERT INTO Funcionarios (" + "nome,endereco,cargo,admissao,foto) " + "VALUES(@nome,@endereco,@cargo,@admissao,@foto)", conSQL); incFunci.Parameters.Add("@nome", SqlDbType.NVarChar, 50).Value = pnome; incFunci.Parameters.Add("@endereco", SqlDbType.NVarChar, 50).Value = pendereco; incFunci.Parameters.Add("@cargo", SqlDbType.NVarChar, 50).Value = pcargo; incFunci.Parameters.Add("@admissao", SqlDbType.DateTime).Value = padmissao; incFunci.Parameters.Add("@foto", SqlDbType.Image, foto.Length).Value = fotoImagem; // Abre a conexao e inclui o BLOB na tabela try { conSQL.Open(); incFunci.ExecuteNonQuery(); } catch (Exception ex) { MessageBox.Show("Erro ao incluir dados : " + ex.Message); } finally { conSQL.Close(); } } |
Temos que definir a rotina auxiliar GetFoto() conforme abaixo; ela retorna um array de bytes;
// **** Le a imagem em um
array de bytes a partir do sistema de arquivos
public
static
byte[] GetFoto(string
caminhoArquivoFoto)
{ FileStream fs = new FileStream(caminhoArquivoFoto, FileMode.Open, FileAccess.Read); BinaryReader br = new BinaryReader(fs); byte[] foto = br.ReadBytes((int)fs.Length); br.Close(); fs.Close(); return foto; }
|
Note que a rotina getFuncionario apenas gera uma imagem em disco a partir do banco de dados . Como exercício que tal obter a imagem e exibir no controle PictureBox. (dica : use a propriedade Image do controle. Ex: picFoto.Image = Bitmap.FromStream(arquivo))
Se você desejar pode colocar um DataGridView no projeto para exibir os funcionários já cadastrados. A rotina para ler os dados e exibir no Grid é dada a seguir:
public void atualizaGrid() { //define a instrução SQL string strSql = "SELECT nome, endereco, cargo FROM Funcionarios"; //cria o objeto command para executar a instruçao sql SqlCommand cmd = new SqlCommand(strSql, conSQL); //abre a conexao conSQL.Open(); //define o tipo do comando cmd.CommandType = CommandType.Text; //cria um dataadapter SqlDataAdapter da = new SqlDataAdapter(cmd); //cria um objeto datatable DataTable funci = new DataTable(); try { //preenche o datatable via dataadapter da.Fill(funci); //atribui o datatable ao datagridview para exibir o resultado gdvFunci.DataSource = funci; } catch (Exception ex) { MessageBox.Show("Erro ao exibir dados " + ex.Message); } finally { conSQL.Close(); } } |
Pegue o projeto completo aqui: usandoDadosBinarios.zip (Sem a base de dados e com código de teste comentado que você pode excluir ou aproveitar)
Eu pretendo voltar a este assunto com mais detalhes em um exemplo usando ASP .NET.
Eu sei é apenas C# mas eu gosto...
José Carlos Macoratti