VB.NET - Criando um aplicação com DataSets não tipados e Provedores de dados 

 

Neste artigo você vai aprender a criar programas que acessam banco de dados relacionais usando datasets não tipados e os provedores de dados ,  ou seja, você vai aprender a criar programas que acessam base de dados relacionais sem usar os assistentes do VS.NET , vai fazer tudo no código. Ao terminar de ler este artigo , se você seguir as orientações e por realmente a mão na massa vai ter aprendido a :

Pronto para começar ?

 

Nota: Este é um artigo para iniciantes e se você precisa criar uma aplicação mais complexa é  melhor usar uma ferramenta ORM como o Entity Framework ou NHibernate. Veja a seção para esses recursos no site.

 

Introdução

 

Um DataSet não tipado é um dataset que não é baseado em um esquema de dados , ou seja , ele é criado via código através da classe DataSet da ADO.NET.

 

Você vai usar os métodos e propriedades da classe DataSet para criar e trabalhar com os datasets não tipados. A figura a seguir exibe o modelo resumido de conexão ADO.NET

 

 

Abaixo temos um figura onde visualiza-se os provedores de dados e o Dataset na arquitetura ADO.NET:

 


Um DataSet é um objeto de dados desconectado e a informação no interior de um dataset esta armazenada em várias coleções : DataRows, DataTables, DataColumns, etc. Conforme figura abaixo:

 

As propriedades usadas com as coleções de dados são :
 
Objeto Propriedade Descrição
DataSet Tables Coleção de tabelas em um DataSet
DataTable Rows Coleção de linhas(rows) em um DataTable
  Columns Coleção de colunas(Columns) em um Datatable
 
Exemplo de código usando estes objetos e propriedades:

'Declara um DataSet
Dim ds As New DataSet    

'Faz referência a uma tabela do DataSet por nome ou por índice.
cboClientes.DataSource = ds.Tables("Clientes")
cboClientes.DataSource = ds.Tables(0)

Na referência acima , se no dataset  houvesse duas tabelas elas seriam referenciadas pelos índices 0 e 1 (obedecendo a ordem na qual foram incluídas no DataSet).

 

Desvantagens na utilização de DataSets não tipados (Untyped DataSets)

Com todas estas desvantagens por que eu estou abordando os dataset não tipados ?

 

Porque em alguns ambientes de desenvolvimento o esquema da fonte de dados não está disponível em tempo de projeto e por que tem muita desenvolvedor que gosta de escrever o seu próprio código.

 

 Criando um projeto de acesso a dados usando datasets não tipados

 

1- Inicie um novo projeto no VS.NET do tipo Windows Application usando a linguagem VB.NET, dando a ele o nome de : dsClientes.

2- Altere o formulário padrão - form1.vb ; altere o seu nome para : frmClientes , construindo no formulário o layout como o indicado na figura abaixo.
 

- Clique com o botão direito sobre o nome do projeto e defina Startup Object como frmClientes

- Os nomes indicados na figura ao lado são os nomes usados nos controles.

- Defina a propriedade ReadOnly como True para todos os controles TextBox

- Defina a propriedade TabStop para o controle cboClientes e para todos os botões de controle como False

- Defina a propriedade TabOrder para os controles TextBox e CboEstado de forma a estarem na ordem correta.

NÃO inclua nenhum objeto Connection, DataAdpater ou DataSet no formulário.

A seguir temos os requisitos de como o sistema deve se comportar:

Criando os objetos Provedores de Dados - Data Provider Objects

Como nossa aplicação é uma aplicação que vai acessar uma fonte de dados devemos dar prioridade para o tratamento a conexão com o banco de dados.

Na plataforma .NET podemos usar os provedores de dados para realizar esta tarefa. Dependendo da base de dados que iremos acessar podemos otimizar esta tarefa escolhendo o provedor adequado. Assim para acessar o SQL Server temos o provedor SqlClient , para acessar uma base de dados Access temos o provedor OleDB , para acessar uma base Oracle temos o SqlOra, etc.

Para detalhes neste assunto leia os artigos :

  1. VB .NET - Os novos provedores de dados .NET
  2. VB .NET - Provedores de Dados

Criando um objeto SQLConnection

Para efetuar uma conexão com uma fonte de dados SQL Server usamos o provedor : SQL Server .NET Data Provider (System.Data.SqlClient)

A primeira coisa a fazer é importar em seu projeto o namespace System.Data.SqlClient

Podemos definir uma string de conexão da seguinte forma :

Imports System.Data.SqlClient

Dim strConn As String = "Server=JCM\Mac;database=Teste; integrated security = False; workstation ID=320; persist security info= False;" _
                                      &  "packet size=4096; user id=mac; password=123456"

Dim conexao = New SqlConnection()
conexao.ConnectionString = strConn

ou você pode fazer armazenar o valor da string de conexão como parte da declaração :

Dim conexao = New SqlConnection(strConn)

Para abrir e fechar uma conexão usamos os métodos :  conexao.Open()  e conexão.Close()

Vamos relacionar o significado dos parâmetros usados na string de conexão para o SQL Server :

É claro que nem todos os parâmetros são obrigatórios. Quais são ?

Vai depender da conexão e do tipo de segurança que você deseja usar. Podemos usar o seguinte código para uma conexão :

Imports System.Data.SqlClient
...
Dim oSQLConn As SqlConnection = New SqlConnection()
oSQLConn.ConnectionString = "Data Source=(local);Initial Catalog=MeuBancoDados;Integrated Security=SSPI"
oSQLConn.Open()

Criando um objeto OleDbConnection

Para criar uma conexão com um banco de dados Access usamos o provedor : OLE DB .NET Data Provider (System.Data.OleDb)

Geralmente temos que informar o valor para DataSource incluindo o caminho do banco de dados:

Abaixo temos um exemplo de conexão usando o provedor OleDb:

Dim oOleDbConnection As OleDb.OleDbConnection
Dim sConnString As String = "Provider=Microsoft.Jet.OLEDB.4.0;" & _
                                            "Data Source=C:\Caminho\NomeBancodeDados.mdb;" & _
                                            "User ID=Admin;" & _
                                            "Password=" 

oOleDbConnection = New OleDb.OleDbConnection(sConnString)
oOleDbConnection.Open()

Onde podemos armazenar a string de conexão ?

A melhor maneira de fazer isto é armazenar a informação sobre a conexão em arquivos externos como um arquivo texto ou arquivo XML. Você pode também armazenar a string de conexão no arquivo de configuração da aplicação. Para saber mais sobre o assunto leia o artigo : NET - Tratando arquivos de configuração.

Criando o código para a aplicação Manutenção de Clientes

A aplicação irá trabalhar com duas tabelas : a tabela Clientes e a tabela Estados. A estrutura de cada uma é exibida abaixo:

Tabela Clientes

tabela Estados

Nossa aplicação vai usar uma conexão com o SQL Server por isto devemos especificar a cláusula Imports para usar o namespace System.DataSqlClient. Vamos definir também Option Strict como On

Option Strict On
Imports
System.Data.SqlClient

e a seguir definir as variáveis usadas no sistema:

Dim blnLoad As Boolean

Dim blnNovaLinha As Boolean

Dim bmClientes As BindingManagerBase

Dim dsClientes As New DataSet

Dim daClientes As New SqlDataAdapter

Dim daEstados As New SqlDataAdapter

Dim con As New SqlConnection

Option Strict On -> Não permite conversão implícita de variáveis de tipos diferentes. O default é Off.

As duas variáveis booleanas serão usadas para controlar a execução do código durante a carga inicial do programa e enquanto um nova linha(registro) for incluída no DataSet..

As demais variáveis objetos serão usadas no tratamento de conexão e dados .

O código do evento Load do formulário:

No evento Load do formulário iremos colocar o código

Private Sub frmClientes_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load


Try

   'defina a variável boolena como true para indicar que o dataaset esta sendo carregado

   blnLoad = True

 
 
   'Chama os procedimentos para criar objetos ADO.NET necessários para a conexao , tabelas e controles

    iniciaObjetos()

    preencheTabelas()

    ligaControles()


    blnLoad =
False
 

    'Chama a rotina passando True - habilita os botões : Salvar , Incluir , Cancelar

    defineBotes(True)
 

    'Define a propriedade BindingContext property do BindingManagerBase

     bmClientes = Me.BindingContext(dsClientes, "Clientes")


Catch
eData As DataException

        MessageBox.Show("Ocorreu o seguinte erro : " & ControlChars.NewLine & eData.Message, "ADO.NET Error", MessageBoxButtons.OK, MessageBoxIcon.Error)

Catch eSQL As SqlException

      Dim intError As Integer

      Dim SqlErrors As SqlErrorCollection = eSQL.Errors

      Dim strErrorResumo As String = "Ocorreu o seguinte erro : "
 

       'Armazena o erro em strErrorResumo

       For intError = 0 To SqlErrors.Count() - 1

            strErrorResumo &= SqlErrors(intError).Number & " – " & SqlErrors(intError).Message & ControlChars.NewLine

        Next intError

        MessageBox.Show(strErrorResumo, "Sql Server Error", MessageBoxButtons.OK, MessageBoxIcon.Error)

Catch eSystem As Exception

       MessageBox.Show("Ocorreu o seguinte erro : " & eSystem.Message, "System Error", MessageBoxButtons.OK, MessageBoxIcon.Error)

End Try
 

End Sub

A rotina iniciaObjetos tem o seguinte código :

Private Sub IniciaObjetos()

   
      'Declara a string de conexão para o o objeto conection

        Dim strConnection As String = "Data Source=(local);Initial Catalog=Macoratti;Integrated Security=SSPI"

      con.ConnectionString = strConnection
 

     'Declare o objeto Comando cmdClientes

       Dim cmdClientes As New SqlCommand

     cmdClientes.Connection = con


     'Armazena a instrução SQL - Select

      Dim strClienteselect As String = "Select Codigo, Nome, Endereco, Cidade, Estado, Cep FROM Clientes ORDER BY Nome"

    cmdClientes.CommandText = strClienteselect


     'Define a propriedade SelectCommand para o dataadapter

     daClientes.SelectCommand = cmdClientes


     'Cria o SqlCommandBuilder

      Dim cbClientes As New SqlCommandBuilder

    cbClientes.DataAdapter = daClientes


    'Cria o objeto Command : cmdEstados

     Dim cmdEstados As New SqlCommand

    cmdEstados.Connection = con


     Dim
strEstadoselect As String = "Select * From Estados Order by Nome"

     cmdEstados.CommandText = strEstadoselect

     daEstados.SelectCommand = cmdEstados
 

End Sub

 

Comentando o código acima:

Se estivéssemos usando datasets tipados não teríamos que nos preocupar com objeto SqlCommand  que no caso armazena o atual comando SQL Select  que quando executado irá retornar um dataset. Ele é gerado automaticamente como parte do objeto DataAdapter e é passado para o objeto Connection quando necessário. No nosso caso temos que criar o objeto SqlCommand.

Existem duas propriedades que precisam ser definidas para um novo objeto SqlCommand : Connection e Commandtext

Outras propriedades:

O procedimento para preencher as tabelas : abre a conexão e preenche os objetos DataAdpater - temos dois : daClientes e dasEstados. Perceba que estou colocando no dataset dsClientes as duas tabelas.

Private Sub preencheTabelas()

    Try

        'Abre a conexão, preenche as tabelas e fecha a conexão.

        con.Open()

        daClientes.Fill(dsClientes, "Clientes")

        daEstados.Fill(dsClientes, "Estados")

       con.Close()


    Catch eSQL As SqlException


        Dim intError As Integer

        Dim SqlErrors As SqlErrorCollection = eSQL.Errors

        Dim strErrorResumo As String = "Error(s): "

 

    'Armazena o erro em strErrorResumo

       For intError = 0 To SqlErrors.Count() - 1

            strErrorResumo &= SqlErrors(intError).Number & " – " & SqlErrors(intError).Message & ControlChars.NewLine

        Next intError

        MessageBox.Show(strErrorResumo, "Sql Server Error", MessageBoxButtons.OK, MessageBoxIcon.Error)

Catch eSystem As Exception

       MessageBox.Show("Ocorreu o seguinte erro : " & eSystem.Message, "System Error", MessageBoxButtons.OK,MessageBoxIcon.Error)

End Try
 

End Sub

 

O procedimento ligaControles vincula os controles ComboBox e TextBox com as colunas das tabelas apropriadas : Clientes e Estados.

As combobox irão exibir o nome completo de cada estado , mas quando uma nova linha for incluída na tabela Clientes, o código do estado é que será armazenado na tabela Clientes através do uso da propriedade ValueMember do combobox.

Private Sub ligaControles()

  

'vinculcao da combobx clientes

cboClientes.DataSource = dsClientes.Tables("Clientes")

cboClientes.DisplayMember = "Nome"


'vinculção simples e complexa da combobox estados

cboEstados.DataSource = dsClientes.Tables("Estados")

cboEstados.DisplayMember = "Nome"

cboEstados.ValueMember = "Estado"

cboEstados.DataBindings.Add("SelectedValue", dsClientes, "Clientes.Estado")


'vinculação simples dos TextBox

txtNome.DataBindings.Add("Text", dsClientes, "Clientes.Nome")

txtEndereco.DataBindings.Add("Text", dsClientes, "Clientes.Endereco")

txtCidade.DataBindings.Add("Text", dsClientes, "Clientes.Cidade")

txtCep.DataBindings.Add("Text", dsClientes, "Clientes.Cep")


End
Sub

 

Agora o código da rotina defineBotoes():

Private Sub defineBotes(ByVal blnValor As Boolean)


'Habilita/Desabilita os botões baseado no parâmetro recebido

btnIncluir.Enabled = blnValor

btnAtualizar.Enabled = blnValor

btnExcluir.Enabled = blnValor

btnCancelar.Enabled = Not blnValor

btnSalvar.Enabled = Not blnValor

btnSair.Enabled = blnValor


End
Sub

Neste momento você já pode executar o projeto que o formulário irá exibir os dados da tabela e as combobox serão preenchidas também.

Esta faltando incluir as funcionalidades referente as ações dos botões e das combobox. Vou fazer isto em breve...

Implementando o código para as ComboBox

Quando o usuário mudar a seleção do nome do cliente na combobox o evento SelectedIndexChanged será disparado. A propriedade SeletecdIndex do controle Combobox é armazenado pela propriedade Position do objeto BindingManagerBase a fim de manter os dados exibidos nas caixas de texto sincronizados com o nome da combobox.

Os botões para manter os dados são habilitados e o foco é desviado para a caixa de texto Nome , que exibe o nome do cliente.

O código que faz todo este tratamento no evento da combobox é o seguinte :

Private Sub cboClientes_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cboClientes.SelectedIndexChanged


'previne execução durante a carga do formulario

If Not blnLoad Then

   bmClientes.Position = cboClientes.SelectedIndex

   defineBotoes(True)

   txtNome.Focus()

End If


End
Sub

O código do botão de comando Incluir

Quando o botão Incluir é pressionado o evento Click será disparado.  No código definimos a variável blnNovaLinha com valor True e chamamos a rotina para 'travar' as caixas de texto com o valor False permitindo assim que possamos incluir dados nas caixas de texto.

O método AddNew de mbClientes inclui uma linha vazia e é chamada a rotina para defineBotes como parâmetro igual a False , com isto habilitamos os botões Salvar e Cancelar enquanto desabitamos todos os outros durante a operação de inclusão de dados. Definimos o valor da combobox para os estados como  cboEstados.SelectedIndex = -1 e colocamos o foco na caixa de texto nome 

Private Sub btnIncluir_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnIncluir.Click


blnNovaLinha =
True


TravaCaixaTextos(False)

bmClientes.AddNew()

defineBotoes(False)

cboEstados.SelectedIndex = -1


txtNome.Focus()


End
Sub

Vamos exibir então o código da rotina TravaCaixaTextos() que foi usada acima:

Private Sub TravaCaixaTextos(ByVal blnvalor As Boolean)


txtNome.ReadOnly = blnvalor

txtEndereco.ReadOnly = blnvalor

txtCidade.ReadOnly = blnvalor

txtCep.ReadOnly = blnvalor


End
Sub

Agora o código do botão - Atualizar :

No evento Click deste botão chamamos a rotina TravaCaixaTextos como parâmetro False para permitir que as alterações nas caixas de texto possam ser feitas. A rotina defineBotes() também é chamada com o parâmetro False para habilitar os botões Salvar e Cancelar

Private Sub btnAtualizar_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnAtualizar.Click

travaCaixaTextos(False)

defineBotoes(False)

End Sub

Excluindo registros

Na exclusão de um registro usamos uma variável para armazenar o nome do cliente ,e , assim poder restaurar os valores se ocorrer alguma exceção.

Ao clicar no botão Excluir uma caixa de mensagem é exibida o usuário para que ele confirme a exclusão. Na confirmação temos:

Se ocorrer alguma  exceção estou fazendo o tratamento para tratar erro devido a concorrência , erro de integridade referencial , erro ADO.NET e erro genérico. Em todos os casos a rotina carregaDataSetClientes() é invocada para restaurar os valores no DataSet.

Abaixo o código completo:

Private Sub btnExcluir_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnExcluir.Click
        Dim strClienteNome As String = txtNome.Text
        Dim dgrResultado As DialogResult = MessageBox.Show("Deseja Excluir " & cboClientes.Text & " ? ", "Confirma a exclusão do cliente", 
                                                       MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2)
        Try
            'se pressionou o botão Sim , então remove o cliente e atualiza o dataset
            If dgrResultado = DialogResult.Yes Then
                Me.Cursor = Windows.Forms.Cursors.WaitCursor
                bmClientes.RemoveAt(bmClientes.Position)
                daClientes.Update(dsClientes, "Clientes")
            End If

        Catch eConstraint As ConstraintException
            'ocorreu um erro nos valores dos dados; o dataset é recarregado
            MessageBox.Show("Ocorreu um erro : " & ControlChars.NewLine & eConstraint.Message, "Erro no valos dos dados",
                                     MessageBoxButtons.OK, MessageBoxIcon.Error)
            recarregaDataSetClientes(strClienteNome)

        Catch eConcorrencia As DBConcurrencyException

            'ocorreu um erro de concorrência na exclusão dos registros
            MessageBox.Show("Ocorreu um erro : " & ControlChars.NewLine & eConcorrencia.Message, "Erro na exclusão do registro.", 
                                     MessageBoxButtons.OK, MessageBoxIcon.Error)
            recarregaDataSetClientes(strClienteNome)

        Catch eDados As DataException

            'ocorreu um erro ADO.NET
            MessageBox.Show("Ocorreu um erro : " & ControlChars.NewLine & eDados.Message, "erro ADO.NET.", 
                                      MessageBoxButtons.OK, MessageBoxIcon.Error)
            recarregaDataSetClientes(strClienteNome)

        Catch eSistema As Exception
            'ocorreu um erro genérico
            MessageBox.Show("Ocorreu um erro : " & ControlChars.NewLine & eSistema.Message, "erro ADO.NET.", MessageBoxButtons.OK, 
                                     MessageBoxIcon.Error)
            recarregaDataSetClientes(strClienteNome)

        Finally
            'define o cursor do mouse e o foco
            Me.Cursor = Windows.Forms.Cursors.Arrow
            cboClientes.Focus()

        End Try

    End Sub

A rotina para recarregar o DataSet

A rotina recarregaDataSetClientes limpa a tabela Clientes do dataset e então chama as rotinas iniciaObjetos e preencheTabelas que reconstroem o dataset.(Esta rotina será chamada também no evento Click do botão Salvar)

Poderíamos melhorar a rotina preencheTabelas pois ela preenche ambas as tabelas ( clientes e estados) e neste caso preencher a tabela estados não é preciso. Deixo como trabalho para você escrever uma rotina separada para preencher cada tabela.

A seguir o código :

    Private Sub recarregaDataSetClientes(ByVal strNome As String)

        'limpa e recarrega o dataset clientes chamando a rotina para criar os objetos command e prencher o datasets
        dsClientes.Tables("Clientes").Clear()
        IniciaObjetos()
        preencheTabelas()
        'reinicia a combobos para o registro clientes
        cboClientes.Text = strNome
        Me.Cursor = Windows.Forms.Cursors.Arrow
    End Sub

O código do botão Salvar esta exibido abaixo. Nele estamos validando os dados invocando a rotina ValidaDados() antes de salvar ; encerrando a edição e atualizando o dataadapter. O tratamento de exceções permite capturar diversos tipos de possíveis exceções.

Private Sub btnSalvar_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnSalvar.Click

        Try

            If validaDados() Then
                Dim strClienteNomeAtual As String = txtNome.Text
                bmClientes.EndCurrentEdit()
                daClientes.Update(dsClientes, "Clientes")

                If blnNovaLinha Then
                    blnNovaLinha = False
                End If

             defineBotoes(True)
                travaCaixaTextos(True)
                cboClientes.Focus()
            End If

        Catch eConstraint As ConstraintException
            'ocorreu um erro nos valores dos dados; o dataset é recarregado
            MessageBox.Show("Ocorreu um erro : " & ControlChars.NewLine & eConstraint.Message, "Erro no valos dos dados", _ 
                                     MessageBoxButtons.OK, MessageBoxIcon.Error)

        Catch eConcorrencia As DBConcurrencyException
            'ocorreu um erro de concorrência na exclusão dos registros
            MessageBox.Show("Ocorreu um erro : " & ControlChars.NewLine & eConcorrencia.Message, "Erro na exclusão do registro.", _ 
                                     MessageBoxButtons.OK, MessageBoxIcon.Error)

        Catch eNulo As NoNullAllowedException
            'um valor nulo para um dado requerido
            MessageBox.Show("Ocorreu um erro : " & ControlChars.NewLine & eNulo.Message, "erro ADO.NET.", _ 
                                      MessageBoxButtons.OK, MessageBoxIcon.Error)

        Catch eDados As DataException
            'ocorreu um erro ADO.NET
            MessageBox.Show("Ocorreu um erro : " & ControlChars.NewLine & eDados.Message, "erro ADO.NET.", _ 
                                      MessageBoxButtons.OK, MessageBoxIcon.Error)

        Catch eSistema As Exception
            'ocorreu um erro genérico
            MessageBox.Show("Ocorreu um erro : " & ControlChars.NewLine & eSistema.Message, "erro ADO.NET.", _
                                       MessageBoxButtons.OK, MessageBoxIcon.Error)

        Finally
            'define o cursor do mouse e o foco
            Me.Cursor = Windows.Forms.Cursors.Arrow
        End Try
    End Sub

 

A função ValidaDados apenas verifica se alguns dados são informados e retorna True ou False.

Private Function validaDados() As Boolean
        
         Dim strErroMsg As String
        If txtNome.Text = "" Then
            strErroMsg = "Informe o Nome."
            txtNome.Focus()
        ElseIf txtCidade.Text = "" Then
            strErroMsg = "Informe a Cidade."
            txtCidade.Focus()
        ElseIf cboEstados.SelectedIndex = -1 Then
            strErroMsg = "Selecione um Estado."
            cboEstados.Focus()
        ElseIf txtCep.Text = "" Then
            strErroMsg = "O Cep é obrigatório."
            txtCep.Focus()
        End If

        If strErroMsg = "" Then
            validaDados = True
        Else
            validaDados = False
            MessageBox.Show(strErroMsg, "Erro", MessageBoxButtons.OK, MessageBoxIcon.Error)
        End If
    End Function

 

Use o seu talento e para completar o projeto ...

Veja os Destaques e novidades do SUPER DVD Visual Basic (sempre atualizado) : clique e confira !

Quer migrar para o VB .NET ?

Quer aprender C# ??

Quer aprender os conceitos da Programação Orientada a objetos ?

Quer aprender o gerar relatórios com o ReportViewer no VS 2013 ?

Referências:


José Carlos Macoratti