VB .NET - Armazenando documentos em um banco de dados


Digitalizar um documento é gerar um arquivo digital que contém bytes e alguma informação sobre sua estrutura. Atualmente podemos ter uma grande variedade de documentos digitais como arquivos de imagens, documentos de texto, arquivos de áudio,etc.

Os arquivos digitais não são um problema em si mesmo, na verdade eles foram criados para ocupar menos espaço e para serem manipulados mais facilmente.

O grande problema com esses arquivos é o seu armazenamento e a sua localização. Geralmente eles acabam se espalhando de forma caótica pelo ambiente, quer em uma máquina local ou pelos servidores de uma rede, o que acaba trazendo um sério problema de localização e tratamento destes arquivos. (Nem sempre podemos compartilhar as pastas de rede)

Por que não usar então um banco de dados para armazenar os documentos digitais centralizando assim a sua localização ?

Neste arquivo eu vou mostrar como podemos armazenar documentos digitais em um banco de dados SQL Server. Vou usar a versão SQL Server 2008 Express Edition como exemplo.

No caso específico do banco de dados SQL Server temos o tipo de dados IMAGE que é um repositório para um array de bytes e que pode ser usado para armazenar e recuperar qualquer tipo de arquivo. Vamos usar esse tipo de dados em nossa tabela de exemplo.

Para realizar o armazenamento e a recuperação dos arquivos na tabela do banco de dados teremos que fazer a serialização dos mesmos que nada mais é que a transformação do conteúdo de cada arquivo em um array de bytes. Dessa forma facilitamos o transporte e a distribuição dos arquivos em uma rede quer seja local quer seja na internet.

Em nosso exemplo vamos armazenar arquivos de imagem, documentos do Word, arquivos PDF em um banco de dados SQL Server.

Os recursos usados serão:

Definindo o banco de dados e a tabela

Vamos iniciar o Visual Studio Express 2012 for Desktop e usá-lo para criar o banco de dados e a tabela usada no exemplo deste artigo.

No menu VIEW clique em Other Windows -> DataBase Explorer para abrir a DataBase Explorer exibindo as conexões existentes com as fontes de dados:

Obs: Na nova versão você já verá o novo estilo visual adotado pelo Visual Studio com a nova logomarca para refletir o design do Metro;

Na janela DataBase Explorer clique com o botão direito do mouse sobre Data Connections e no menu suspenso clique em Add Connection...

Na janela Add Connection, em Server Name,informe (localdb)\v11.0 e em Select or enter a database name informe o nome Gedoc que será o nome do nosso banco de dados.

Clicando no botão OK será aberta a janela informando que o banco de dados não existe e solicitando a sua confirmação para criá-lo. Clique no botão Sim:

O Microsoft SQL Server 2012 Express LocalDB é um modo de execução do SQL Server Express destinado a desenvolvedores de programas.

A instalação do LocalDB copia um conjunto mínimo de arquivos necessários para iniciar o mecanismo de Banco de Dados do SQL Server.

Quando o LocalDB é instalado, os desenvolvedores iniciam uma conexão usando uma cadeia de conexão especial.

Na conexão, a infraestrutura necessária do SQL Server é criada e iniciada automaticamente, permitindo que o aplicativo use o banco de dados sem tarefas de configuração complexas ou demoradas.

O Developer Tools pode fornecer aos desenvolvedores um mecanismo de Banco de Dados do SQL Server que permite que eles gravem
e testem o código Transact-SQL sem precisar gerenciar uma instância de servidor inteira do SQL Server. Uma instância do SQL Server Express LocalDB é gerenciada com o utilitário SqlLocalDB.exe.

O SQL Server Express LocalDB deve ser usado em lugar do recurso de instância de usuário do SQL Server Express, que ficou obsoleto.

Você verá na janela DataBase Explorer a conexão com o banco de dados recém criado. Vamos criar a nossa tabela no banco de dados.

Expanda os objetos da conexão e clique com o botão direito do mouse sobre Tables;

No menu suspenso clique em Add New Table;

Na janela do descritor SQL defina a estrutura da tabela com os seguintes campos:

Se você clicar sobre a conexão com o banco de dados Gedoc poderá ver na janela de propriedades a string de conexão definida:

Criando o projeto no Visual Studio for Desktop

Abra o Visual Studio for Desktop e no menu File clique em New Project e selecione a linguagem Visual Basic e o template Windows Forms Application informando o nome Gedoc;

Será criada uma solução contendo o projeto Windows Forms com um formulário form1.vb e arquivo de configuração App.Config.

Altere o nome do formulário form1.vb criado para frmMenu.vb e a partir da ToolBox inclua um controle MenuStrip no formulário.

O formulário frmMenu será um container MDI e para isso vamos definir a sua propriedade IsMDIContainer como True;

A seguir vamos criar as opções do menu no formulário conforme mostra a figura a seguir:

Na opção Salvar iremos abrir o formulário frmSalvar que permitirá ao usuário selecionar um arquivo e salvar o arquivo no banco de dados.

Na opção Exibir o formulário frmCarregar irá recuperar um documento salvo no banco de dados e conforme o seu formato exibi-lo.

A opção Sobre exibe informações da aplicação via formulário frmSobre.

Vamos criar então os três formulários que serão usados na aplicação.

No menu Project clique em Add New Item e a seguir e selecione o template Windows Forms Application.

Dessa forma crie os formulários : frmSalvar.vb , frmCarregar.vb e frmSobre.vb.

Vamos definir no arquivo App.Config a string de conexão definida para o banco de dados Gedoc conforme o código abaixo:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup>
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
    </startup>
  <configSections>
  </configSections>
  <connectionStrings>
    <add name="ConexaoDigital"
         connectionString=
"Data Source=(localdb)\v11.0;Initial Catalog=Gedoc;Integrated Security=True"
         providerName=
"System.Data.SqlClient"
         />
  </connectionStrings>
</configuration>

Vamos agora incluir um Módulo em nossa solução. No menu Project clique em Add New Item;

A seguir selecione o template Module informando o nome Geral;

Neste módulo vamos definir 3 métodos e 1 estrutura que serão visíveis em todo projeto. Os métodos serão usados para fazer o tratamento do arquivo realizando a serialização e recuperando os bytes para exibição e a estrutura irá conter 3 propriedades que representam um documento conforme definimos na tabela Documentos:

No módulo Geral defina uma estrutura conforme o código abaixo:

Structure Documento

    Public Property ID As Integer
    Public Property Nome As String
    Public Property Conteudo As Byte()

End Structure
A estrutura documento possui os campos definidos como propriedades para armazenar os dados de um documento.

A seguir inclua o código dos 3 métodos :

  1. GetBytes() - serializa o arquivo retornando um array de byte;
  2. GetDocumento() - Retorna os dados de um documento;
  3. GetDadosArquivo - Retorna o nome documento;
Imports System.IO
Imports Gedoc.Documento
Module Geral

    ' Serializa o arquivo retornando array de bytes
    Public Function GetBytes(ByVal _nomeArquivo As String) As Byte()
        ' obtém os dados do arquivo
        Try
            Using arquivo As New FileStream(_nomeArquivo, FileMode.Open, FileAccess.Read)
                Dim br As New BinaryReader(arquivo)
                Dim retorno As Byte() = br.ReadBytes(CInt(arquivo.Length))
                Return retorno
            End Using
        Catch ex As Exception
            Throw ex
        End Try
    End Function

    ' Retorna os dados do documento a partir do arquivo informado
    Public Function GetDocumento(ByVal _nomeArquivo As String) As Documento
        Try
            ' executa o método que obtém informações do arquivo
            Dim documento As Documento = GetDadosArquivo(_nomeArquivo)
            ' serializa o arquivo
            documento.Conteudo = GetBytes(_nomeArquivo)
            Return documento
        Catch ex As Exception
            Throw ex
        End Try
    End Function

    Private Function GetDadosArquivo(ByVal _nomeArquivo As String) As Documento
        Try
            ' instancia o objeto de retorno, le os dados e retorna
            Return New Documento() With {.Nome = _nomeArquivo}
        Catch ex As Exception
            Throw
        End Try
    End Function

Neste momento nossa solução terá todos itens necessários para o seu funcionamento e na janela Solution Explorer devermos ver a seguinte estrutura:

Definindo o menu de opções

Vamos começar com o formulário frmMenu que exibir as opções da aplicação permitindo ao usuário salvar e recuperar documentos.

Inclua o código abaixo no formulário frmMenu.vb:

Public Class FrmMenu

    Private Sub SalvarToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles SalvarToolStripMenuItem.Click
        Dim frmSalvar As New frmSalvar()
        frmSalvar.MdiParent = Me
        frmSalvar.Show()
    End Sub

    Private Sub ExibirToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles ExibirToolStripMenuItem.Click
        Dim frmExibir As New frmCarregar()
        frmExibir.MdiParent = Me
        frmExibir.Show()
    End Sub

    Private Sub SobreToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles SobreToolStripMenuItem.Click
        Dim frmAbout As New frmSobre()
        frmAbout.MdiParent = Me
        frmAbout.Show()
    End Sub
End Class

No código acima estamos criando uma instância do formulário que desejamos exibir e a seguir definimos qual o formulário pai desta instância definindo a propriedade MdiParent como Me. A seguir exibimos o formulário desejado.

Salvando documentos

No formulário frmSalvar.vb inclua os seguintes controles:

Neste formulário defina as declarações dos namespaces que iremos usar :

Imports System.Data.SqlClient
Imports System.IO
Imports System.Security

Logo após a declaração do formulário vamos definir as propriedades _conexao do tipo SqlConnection e _documento do tipo Documento:

Dim _conexao As SqlConnection
Dim _documento As Documento

No botão btnlocalizar vamos incluir o código que permite ao usuário selecionar um documento para ser salvo no banco de dados.

Para isso vamos abrir uma janela Open File Dialog a partir da qual o usuário irá selecionar o arquivo:

Private Sub btnLocalizar_Click(sender As Object, e As EventArgs) Handles btnLocalizar.Click
        'define as propriedades do controle 
        'OpenFileDialog
        Me.ofd1.Multiselect = False
        Me.ofd1.Title = "Selecionar Documento"
        ofd1.InitialDirectory = "C:\dados\"
        'aplica um filtro 
        ofd1.Filter = "Arquivos (*.PDF;*.AVI;*.MP3;*.BMP;*.JPG;*.GIF,*.PNG,*.TIFF)|*.PDF;*.AVI;*.MP3;*.BMP;*.JPG;*.GIF;*.PNG;*.TIFF|" & "Todos (*.*)|*.*"
        ofd1.CheckFileExists = True
        ofd1.CheckPathExists = True
        ofd1.FilterIndex = 2
        ofd1.RestoreDirectory = True
        Dim dr As DialogResult = Me.ofd1.ShowDialog()

        If dr = System.Windows.Forms.DialogResult.OK Then
            If String.IsNullOrEmpty(ofd1.FileName) Then
                Return
            End If
            Try
                txtDocumento.Text += ofd1.FileName
                'define os dados do documento
                _documento = GetDocumento(ofd1.FileName)
                _documento.Conteudo = GetBytes(ofd1.FileName)
                _documento.Nome = txtDocumento.Text
                btnSalvar.Enabled = True

            Catch ex As SecurityException
                ' O usuário  não possui permissão para ler arquivos
                MessageBox.Show((("Erro de segurança Contate o administrador de segurança da rede." &
                                                                vbLf & vbLf & "Mensagem : ") + ex.Message &
                                                                vbLf & vbLf & "Detalhes (enviar ao suporte):" & vbLf & vbLf) + ex.StackTrace)
            Catch ex As Exception
                ' Não pode carregar a imagem (problemas de permissão)
                MessageBox.Show(("Não é possível exibir a imagem : " &
                                                                ofd1.FileName & ". Você pode não ter permissão para ler o arquivo , ou " _
                                                                 & " ele pode estar corrompido." & vbLf & vbLf & "Erro reportado : ") + ex.Message)
            End Try
        End If
    End Sub

Após selecionar o arquivo e obter os dados documento o usuário deverá clicar no botão Salvar para armazenar o documento no banco de dados. O código deste botão é visto abaixo:

Private Sub btnSalvar_Click(sender As Object, e As EventArgs) Handles btnSalvar.Click
        SalvarDocumento()
End Sub

Public Sub SalvarDocumento()
        ' Cria o comando SQL
        Dim commandString As String = "INSERT INTO Documentos (Nome,Conteudo) VALUES (@Nome, @Conteudo)"
        ' define a string de conexão com o banco de dados
        _conexao = New SqlConnection("Data Source=(localdb)\v11.0;Initial Catalog=Gedoc;Integrated Security=True")
        Try
            'Abre a conexão com o banco de dados
            _conexao.Open()
            Dim command As New SqlCommand(commandString, _conexao)
            ' adiciona os parâmetros
            command.Parameters.AddWithValue("Nome", _documento.Nome)
            command.Parameters.AddWithValue("Conteudo", _documento.Conteudo)
            ' Grava o documento no banco de dados
            command.ExecuteNonQuery()
            MessageBox.Show("Arquivo salvo com sucesso no banco de dados !")
            btnSalvar.Enabled = False
        Catch ex As Exception
            Throw ex
        Finally
            _conexao.Close()
        End Try
 End Sub

Recuperando documentos

No formulário frmCarregar.vb iremos recuperar e exibir um documento armazenado no banco de dados.

Para facilitar a vida do usuário vamos exibir o conteúdo da tabela Documentos em um controle DataGridView de forma que o usuário poderá selecionar uma linha do controle e o nome do arquivo será exibido em um TextBox(txt.

Clicando no botão Exibir iremos verificar o formato do arquivo e se o mesmo for um arquivo de imagem iremos exibi-lo em um controle PictureBox e se form de outro formato iremos abrir o documento usando a classe Process do namespace System.Diagnostics.

Usando a classe Process você pode obter informações sobre processos e aplicações , pode também usá-la para criar uma instância de uma aplicação ou parar um processo que esta sendo executado. A grosso modo poderíamos dizer que a classe Process é a sucessora do comando Shell do VB6 (lembra dele ?)

Vamos incluir no formulário frmCarregar os seguintes controles:

Conforme o leiaute abaixo:

Vamos declara os namespaces usados no formulário:

Imports System.Data.SqlClient
Imports System.IO
Imports System.IO.File
Imports System.Diagnostics

No início do formulário vamos declarar as variáveis para tratar a conexão e o documento:

Dim _conexao As SqlConnection
Dim _documento As Documento

No evento Load do formulário temos o código que acessa a tabela Documentos e exibe os dados no controle DataGridView:

Private Sub frmCarregar_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Try
            ' cria comando SQL
            Dim commandString As String = "Select id, nome from Documentos"
            ' Cria conexao com o banco de dados
            _conexao = New SqlConnection("Data Source=(localdb)\v11.0;Initial Catalog=Gedoc;Integrated Security=True")
            Dim command As New SqlCommand(commandString, _conexao)
            ' Cria um dataAdapter
            Dim adapter As New SqlDataAdapter(command)
            ' preenche o DataTable.
            Dim dataTable As New DataTable()
            'exibe os documentos no datagridview
            adapter.Fill(dataTable)
            dgvDocumentos.DataSource = dataTable
        Catch ex As Exception
            Throw ex
        Finally
            _conexao.Close()
        End Try
    End Sub

No evento CellEnter do datagridview temos o código que obtém o nome e o local do documento e o código do documento exibindo-os nos controles do formulário:

 Private Sub dgvDocumentos_CellEnter(sender As Object, e As DataGridViewCellEventArgs) Handles dgvDocumentos.CellEnter
        'obtem o valor da primeira e segunda colunas da célula selecionada e atribui ao textbox e label no formulario
        txtDocumento.Text = dgvDocumentos.Rows(e.RowIndex).Cells(1).Value().ToString
        lblID.Text = dgvDocumentos.Rows(e.RowIndex).Cells(0).Value().ToString
    End Sub

No botão Exibir recuperamos o documento e exibimos o seu conteúdo:

Private Sub btnExibir_Click(sender As Object, e As EventArgs) Handles btnExibir.Click
        Try
            _documento = New Documento
            _documento.Conteudo = RecuperarDocumento()
            'obtem a extensão do arquivo
            Dim fInfo As New FileInfo(txtDocumento.Text)
            Dim extensao As String = fInfo.Extension

            ' cria um objeto MemoryStream a partir dos dados do documento
            Dim novoStream As New MemoryStream(_documento.Conteudo)

            'define as extensões para imagem 
            Dim Extensoes As New List(Of String)(New String() {".jpg", ".bmp", ".png", ".gif"})

            'verifica se a extensão é de uma formato de imagem e exige a imagem
            If Extensoes.Contains(extensao) Then
                picDocumento.Image = System.Drawing.Image.FromStream(novoStream)
                picDocumento.SizeMode = PictureBoxSizeMode.StretchImage
            Else
                Using fs As New FileStream(txtDocumento.Text, FileMode.OpenOrCreate, FileAccess.Write)
                    fs.Write(_documento.Conteudo, 0, _documento.Conteudo.Length)
                    fs.Flush()
                    fs.Close()
                End Using
                Process.Start(txtDocumento.Text)
            End If
        Catch ex As Exception
            Throw ex
        End Try
    End Sub

O código que usamos para exibir um documento que não é uma imagem é :

Using fs As New FileStream(txtDocumento.Text, FileMode.OpenOrCreate, FileAccess.Write)
     fs.Write(_documento.Conteudo, 0, _documento.Conteudo.Length)
     fs.Flush()
     fs.Close()
End Using
Process.Start(txtDocumento.Text)

A classe Process inicia o aplicativo associado ao tipo de documento armazenado permitindo que seja exibido os mais diversos tipos de documentos.

A rotina RecuperarDocumento() obtém o conteúdo do documento armazenado como um array de bytes a partir do seu código:

 Public Function RecuperarDocumento() As Byte()
        Try
            ' cria um comando SQL filtrando pelo numero de codigo (Id) do documento
            Dim commandString As String = "Select conteudo from Documentos where id = " & Convert.ToInt32(lblID.Text)
            ' Cria uma conexão com o banco de dados
            _conexao = New SqlConnection("Data Source=(localdb)\v11.0;Initial Catalog=Gedoc;Integrated Security=True")
            Dim command As New SqlCommand(commandString, _conexao)
            ' Cria um data adapter.
            Dim adapter As New SqlDataAdapter(command)
            ' preenche um DataTable.
            Dim dataTable As New DataTable()
            adapter.Fill(dataTable)
            ' verifica se existem registros no banco de dados com o criterio 
            If dataTable.Rows.Count > 0 Then
                ' O documento esta armazenado na forma de bytes na coluna conteudo
                ' Retorna esses bytes do primeiro registro encontrado para um novo buffer.
                Dim buffer() As Byte = CType(dataTable.Rows(0)("Conteudo"), Byte())
                Return buffer
            Else
                MessageBox.Show("Arquivo não localizado!")
                Return Nothing
            End If
        Catch ex As Exception
            Throw ex
        Finally
            _conexao.Close()
        End Try
    End Function

Abaixo vemos o projeto em execução:

O objetivo básico do artigo foi cumprido : mostrei como podemos armazenar e recuperar documentos em um banco de dados.

A forma de implementação usada não esta aderente às boas práticas e precisa ser melhorada. Temos que separar responsabilidades, evitar código duplicado, facilitar a manutenção, etc.

Em outro artigo eu pretendo mostrar como podemos ajustar a aplicação ajustando às boas práticas de programação com aplicação de conceitos básicos da orientação a objetos, sim porque neste estágio estamos longe de ter uma aplicação OOP.

Pegue o projeto completo aqui: Gedoc.zip

Mateus 8: 20 Respondeu-lhe Jesus: As raposas têm covis, e as aves do céu têm ninhos; mas o Filho do homem não tem onde reclinar a cabeça.

João 2:6 “Aquele que diz que permanece nele, esse deve também andar assim como ele andou”

1 Pedro 2:11 “Amados, exorto-vos, como peregrinos e forasteiros que sois, a vos absterdes das paixões carnais, que fazem guerra contra a alma.”

Efésios 4:1-2 - “Rogo-vos, pois, eu, o prisioneiro no Senhor, que andeis de modo digno da vocação a que fostes chamados, como toda a humildade e mansidão, com longanimidade, suportando-vos uns aos outros em amor.”

Efésios 5:8 - Pois, outrora, éreis trevas, porém, agora, sois luz no Senhor; andai como filhos da luz.”

Jesus não teve nenhum bem terreno, não procurou ajuntar riqueza nessa terra; devemos andar como ele andou, como peregrinos e forasteiros nesta terra com humildade e mansidão. Muito diferente do que vemos espalhado por ai sendo propalado pelos falsos profetas e homens cheios de ganância que só pensam nas coisas dessa terra e deturpam a palavra da verdade.

Referências:


José Carlos Macoratti