Skip to content

7. Acesso à base de dados

7.1. Geral

Existem muitas bases de dados disponíveis para plataformas Windows. Para aceder às mesmas, as aplicações utilizam programas chamados controladores.

No diagrama acima, o controlador possui duas interfaces:

  • a interface I1 apresentada à aplicação
  • a interface I2 para a base de dados

Para evitar que uma aplicação escrita para a base de dados B1 tenha de ser reescrita caso seja migrada para uma base de dados diferente, a B2, foram envidados esforços de normalização na interface I1. Se forem utilizadas bases de dados que utilizem controladores «normalizados», a base de dados B1 será fornecida com o controlador P1, a base de dados B2 com o controlador P2, e a interface I1 destes dois controladores será idêntica. Assim, a aplicação não precisará de ser reescrita. Por exemplo, é possível migrar uma base de dados ACCESS para uma base de dados MySQL sem alterar a aplicação.

Existem dois tipos de controladores padronizados:

  • Drivers ODBC (Open DataBase Connectivity)
  • Drivers OLE DB (Object Linking and Embedding DataBase)

Os controladores ODBC proporcionam acesso a bases de dados. As fontes de dados para os controladores OLE DB são mais variadas: bases de dados, sistemas de e-mail, diretórios, etc. Não há limites. Qualquer fonte de dados pode ser objeto de um controlador OLE DB, se um fornecedor assim o decidir. A vantagem é obviamente significativa: tem acesso uniforme a uma grande variedade de dados.

A plataforma .NET inclui dois tipos de classes de acesso a dados:

  1. Classes SQL Server.NET
  2. Classes OLE DB.NET

As primeiras classes permitem o acesso direto ao SGBD SQL Server da Microsoft sem um controlador intermédio. As segundas classes permitem o acesso a fontes de dados OLE DB.

Image

A plataforma .NET é fornecida (em maio de 2002) com três controladores OLE DB para o SQL Server, Oracle e Microsoft Jet (Access), respetivamente. Se pretender trabalhar com uma base de dados que tenha um controlador ODBC mas não um controlador OLE DB, não o poderá fazer. Assim, não poderá trabalhar com o SGBD MySQL, que (em maio de 2002) não fornece um controlador OLE DB. No entanto, existe um conjunto de classes que permite o acesso a fontes de dados ODBC: as classes odbc.net. Estas não estão incluídas por predefinição no SDK e têm de ser descarregadas do site da Microsoft. Nos exemplos que se seguem, utilizaremos principalmente estas classes ODBC, uma vez que a maioria das bases de dados no Windows vem com um controlador deste tipo. Aqui, por exemplo, está uma lista de controladores ODBC instalados numa máquina com Windows 2000 (Menu Iniciar/Definições/Painel de Controlo/Ferramentas Administrativas):

Image

Selecione o ícone «Fonte de dados ODBC»:

Image

7.2. As duas formas de utilizar uma fonte de dados

A plataforma .NET permite-lhe utilizar uma fonte de dados de duas formas diferentes:

  1. modo conectado
  2. modo desconectado

No modo conectado, a aplicação

  1. abre uma ligação à fonte de dados
  2. trabalha com a fonte de dados no modo de leitura/gravação
  3. fecha a ligação

No modo offline, a aplicação

  1. abre uma ligação à fonte de dados
  2. recupera uma cópia na memória de todos ou parte dos dados da fonte
  3. fecha a conexão
  4. trabalha com a cópia na memória dos dados no modo de leitura/gravação
  5. quando o trabalho estiver concluído, abre uma ligação, envia os dados modificados para a fonte de dados para que possam ser atualizados e fecha a ligação

Em ambos os casos, é o processo de processamento e atualização dos dados que leva tempo. Imagine que estas atualizações são realizadas por um utilizador que introduz dados; este processo pode demorar dezenas de minutos. Durante todo este tempo, no modo conectado, a ligação à base de dados é mantida e as alterações são refletidas imediatamente. No modo offline, não há ligação à base de dados enquanto os dados estão a ser atualizados. As alterações são feitas apenas na cópia na memória. São refletidas na fonte de dados de uma só vez quando tudo estiver concluído.

Quais são as vantagens e desvantagens dos dois métodos?

  • Uma ligação consome recursos do sistema. Se houver muitas ligações simultâneas, o modo offline ajuda a minimizar a sua duração. É o caso das aplicações web com milhares de utilizadores.
  • A desvantagem do modo offline é o tratamento delicado das atualizações simultâneas. O utilizador U1 recupera dados no momento T1 e começa a modificá-los. No momento T2, o utilizador U2 também acede à fonte de dados e recupera os mesmos dados. Entretanto, o utilizador U1 modificou alguns dados, mas ainda não os transmitiu para a fonte de dados. O U2 está, portanto, a trabalhar com dados, alguns dos quais estão incorretos. As classes .NET oferecem soluções para gerir este problema, mas não é fácil de resolver.
  • No modo conectado, as atualizações simultâneas de dados por vários utilizadores normalmente não representam um problema. Uma vez que a ligação à base de dados é mantida, a própria base de dados gere estas atualizações simultâneas. Assim, a Oracle bloqueia uma linha na base de dados assim que um utilizador a modifica. Esta permanecerá bloqueada — e, portanto, inacessível a outros utilizadores — até que o utilizador que a modificou confirme a alteração ou a reverta.
  • Se for necessário partilhar dados através da rede, deve optar pelo modo offline. Este modo fornece um instantâneo dos dados num objeto chamado conjunto de dados, que funciona como uma base de dados autónoma. Este objeto pode ser partilhado através da rede entre máquinas.

Vamos primeiro examinar o modo conectado.

7.3. Aceder aos dados no modo conectado

7.3.1. As bases de dados no exemplo

Estamos a considerar uma base de dados do Access chamada articles.mdb que contém apenas uma tabela chamada ARTICLES com a seguinte estrutura:

nome
tipo
código
código do artigo de 4 caracteres
nome
o seu nome (cadeia de caracteres)
preço
o seu preço (real)
stock_atual
stock atual (inteiro)
stock_mínimo
o stock mínimo (número inteiro) abaixo do qual o artigo deve ser reabastecido

O seu conteúdo inicial é o seguinte:

Image

Iremos utilizar esta base de dados através de um controlador ODBC e de um controlador OLE DB para demonstrar a semelhança entre as duas abordagens e porque dispomos de ambos os tipos de controladores para o ACCESS.

Iremos também utilizar uma base de dados MySQL denominada DBARTICLES, que possui a mesma tabela única ARTICLES, o mesmo conteúdo e é acedida através de um controlador ODBC, para demonstrar que a aplicação escrita para utilizar a base de dados Access não precisa de ser modificada para utilizar a base de dados MySQL. A base de dados DBARTICLES é acessível a um utilizador denominado admarticles com a palavra-passe mdparticles. A captura de ecrã seguinte mostra o conteúdo da base de dados MySQL:

C:\mysql\bin>mysql --database=dbarticles --user=admarticles --password=mdparticles
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 3 to server version: 3.23.49-max-debug

Type 'help' for help.


mysql> show tables;
+----------------------+
| Tables_in_dbarticles |
+----------------------+
| articles             |
+----------------------+
1 row in set (0.01 sec)

mysql> select * from articles;
+------+--------------------------------+------+--------------+---------------+
| code | nom                            | prix | stock_actuel | stock_minimum |
+------+--------------------------------+------+--------------+---------------+
| a300 | vÚlo                           | 2500 |           10 |             5 |
| b300 | pompe                          |   56 |           62 |            45 |
| c300 | arc                            | 3500 |           10 |            20 |
| d300 | flÞches - lot de 6             |  780 |           12 |            20 |
| e300 | combinaison de plongÚe         | 2800 |           34 |             7 |
| f300 | bouteilles d'oxygÞne           |  800 |           10 |             5 |
+------+--------------------------------+------+--------------+---------------+
6 rows in set (0.02 sec)

mysql> describe articles;
+---------------+-------------+------+-----+---------+-------+
| Field         | Type        | Null | Key | Default | Extra |
+---------------+-------------+------+-----+---------+-------+
| code          | text        | YES  |     | NULL    |       |
| nom           | text        | YES  |     | NULL    |       |
| prix          | double      | YES  |     | NULL    |       |
| stock_actuel  | smallint(6) | YES  |     | NULL    |       |
| stock_minimum | smallint(6) | YES  |     | NULL    |       |
+---------------+-------------+------+-----+---------+-------+
5 rows in set (0.00 sec)

mysql> exit
Bye

Para definir a base de dados ACCESS como uma fonte de dados ODBC, proceda da seguinte forma:

  • Abra o Gestor de Fontes de Dados ODBC, conforme mostrado acima, e selecione o separador DSN do utilizador (DSN = Nome da Fonte de Dados)

Image

  • Adicione uma fonte utilizando o botão Adicionar, especifique que esta fonte é acessível através de um controlador do Access e clique em Concluir:

Image

  • Dê à fonte de dados o nome «articles-access», introduza uma descrição à sua escolha e utilize o botão «Selecionar» para especificar o ficheiro .mdb da base de dados. Conclua clicando em «OK».

Image

A nova fonte de dados aparecerá então na lista de DSNs do utilizador:

Image

Para definir a base de dados MySQL DBARTICLES como uma fonte de dados ODBC, proceda da seguinte forma:

  • Abra o Gestor de Fontes de Dados ODBC, conforme mostrado acima, e selecione o separador DSN do utilizador. Adicione uma nova fonte de dados utilizando Adicionar e selecione o controlador ODBC do MySQL.

Image

  • Clique em Concluir. Será então apresentada uma página de configuração da fonte MySQL:

54321

Image

  • Em (1), atribua um nome à sua fonte de dados ODBC
  • Em (2), especifique a máquina onde o servidor MySQL está localizado. Aqui, introduzimos `localhost` para indicar que está na mesma máquina que a nossa aplicação. Se o servidor MySQL estivesse numa máquina remota `M`, introduziríamos o seu nome aqui, e a nossa aplicação funcionaria então com uma base de dados remota sem quaisquer alterações.
  • Em (3), introduza o nome da base de dados. Aqui, chama-se DBARTICLES.
  • Em (4), introduza o nome de utilizador admarticles e, em (5), a palavra-passe mdparticles.

7.3.2. Utilização de um controlador ODBC

Numa aplicação que utilize uma base de dados no modo conectado, geralmente são necessários os seguintes passos:

  1. Ligar-se à base de dados
  2. Envio de consultas SQL para a base de dados
  3. Receber e processar os resultados dessas consultas
  4. Encerrar a ligação

Os passos 2 e 3 são executados repetidamente, sendo que a ligação só é encerrada no final das operações da base de dados. Trata-se de um padrão relativamente comum com o qual poderá estar familiarizado se já tiver trabalhado com uma base de dados de forma interativa. Estes passos são os mesmos, quer a base de dados seja acedida através de um controlador ODBC ou de um controlador OLE DB. Segue-se um exemplo que utiliza as classes .NET para gerir fontes de dados ODBC. O programa chama-se liste e recebe como parâmetro o nome DSN de uma fonte de dados ODBC que contém uma tabela denominada ARTICLES. Em seguida, apresenta o conteúdo desta tabela:

dos>liste
syntaxe : pg dsnArticles

dos>liste articles-access

----------------------------------------
code,nom,prix,stock_actuel,stock_minimum
----------------------------------------

a300 vélo                           2500 10 5
b300 pompe                          56 62 45
c300 arc                            3500 10 20
d300 flèches - lot de 6             780 12 20
e300 combinaison de plongée         2800 34 7
f300 bouteilles d'oxygène           800 10 5

dos>liste mysql-artices
Erreur d'exploitation de la base de données (ERROR [IM002] [Microsoft][ODBC Driver Manager] Data source name not found and no default driver specified)

dos>liste mysql-articles

----------------------------------------
code,nom,prix,stock_actuel,stock_minimum
----------------------------------------

a300 vélo                           2500 10 5
b300 pompe                          56 62 45
c300 arc                            3500 10 20
d300 flèches - lot de 6             780 12 20
e300 combinaison de plongée         2800 34 7
f300 bouteilles d'oxygène           800 10 5

A partir dos resultados acima, podemos ver que o programa listou o conteúdo tanto da base de dados ACCESS como da base de dados MySQL. Vamos agora examinar o código deste programa:


' options
Option Explicit On 
Option Strict On

' namespaces
Imports System
Imports System.Data
Imports Microsoft.Data.Odbc
Imports Microsoft.VisualBasic
 
Module db1
    Sub main(ByVal args As String())
        ' application console
        ' displays the contents of a ARTICLES table in a DSN database 
        ' whose name is passed in parameter
        Const syntaxe As String = "syntaxe : pg dsnArticles"
        Const tabArticles As String = "articles"        ' table of articles
 
        ' parameter verification
        ' do we have 1 parameter
        If args.Length <> 1 Then
            ' error msg
            Console.Error.WriteLine(syntaxe)
            ' end
            Environment.Exit(1)
        End If
 
        ' parameter is retrieved
        Dim dsnArticles As String = args(0)                ' the DSN database
        ' preparing the connection to the comic
        Dim articlesConn As OdbcConnection = Nothing        ' the connection
        Dim myReader As OdbcDataReader = Nothing            ' the data reader
 
        ' attempt to access the database
        Try
            ' base connection chain
            Dim connectString As String = "DSN=" + dsnArticles + ";"
            articlesConn = New OdbcConnection(connectString)
            articlesConn.Open()
 
            ' execution of a SQL command
            Dim sqlText As String = "select * from " + tabArticles
            Dim myOdbcCommand As New OdbcCommand(sqlText)
            myOdbcCommand.Connection = articlesConn
            myReader = myOdbcCommand.ExecuteReader()
 
            ' Using the recovered table
            ' column display
            Dim ligne As String = ""
            Dim i As Integer
            For i = 0 To (myReader.FieldCount - 1) - 1
                ligne += myReader.GetName(i) + ","
            Next i
            ligne += myReader.GetName(i)
            Console.Out.WriteLine((ControlChars.Lf + "".PadLeft(ligne.Length, "-"c) + ControlChars.Lf + ligne + ControlChars.Lf + "".PadLeft(ligne.Length, "-"c) + ControlChars.Lf))
 
            ' data display
            While myReader.Read()
                ' current line operation
                ligne = ""
                For i = 0 To myReader.FieldCount - 1
                    ligne += myReader(i).ToString + " "
                Next i
                Console.WriteLine(ligne)
            End While
        Catch ex As Exception
            Console.Error.WriteLine(("Erreur d'exploitation de la base de données " + ex.Message + ")"))
            Environment.Exit(2)
        Finally
            ' drive lock
            myReader.Close()
            ' locking connection
            articlesConn.Close()
        End Try
    End Sub
End Module

As classes de gestão de fontes ODBC estão localizadas no namespace Microsoft.Data.Odbc, que deve, portanto, ser importado. Além disso, várias classes estão localizadas no namespace System.Data.


Imports System.Data
Imports Microsoft.Data.Odbc

Os namespaces utilizados pelo programa encontram-se em assemblies diferentes. Compile o programa utilizando o seguinte comando:

dos>vbc /r:microsoft.data.odbc.dll /r:microsoft.visualbasic.dll /r:system.dll /r:system.data.dll db1.vb

7.3.2.1. A fase de ligação

Uma ligação ODBC utiliza a classe OdbcConnection. O construtor desta classe recebe como parâmetro o que se denomina «cadeia de ligação». Trata-se de uma sequência de caracteres que define todos os parâmetros necessários para estabelecer a ligação à base de dados. Podem existir muitos desses parâmetros, o que torna a cadeia complexa. A cadeia tem o formato «param1=valor1;param2=valor2;...;paramj=valorj;». Aqui estão alguns parâmetros possíveis para paramj:

uid
nome de utilizador do utilizador que irá aceder à base de dados
password
a palavra-passe deste utilizador
dsn
o nome DSN da base de dados, se existir
fonte de dados
nome da base de dados a que se está a aceder
...
 

Se definir uma fonte de dados como uma fonte de dados ODBC utilizando o Gestor de Fontes de Dados ODBC, estes parâmetros já terão sido especificados e guardados. Nesse caso, basta passar o parâmetro DSN, que fornece o nome DSN da fonte de dados. É isso que se faz aqui:


        ' preparing the connection to the comic
        Dim articlesConn As OdbcConnection = Nothing        ' the connection
        Dim myReader As OdbcDataReader = Nothing        ' the data reader
        Try
            ' attempt to access the database
            ' base connection chain
            Dim connectString As String = "DSN=" + dsnArticles + ";"
            articlesConn = New OdbcConnection(connectString)
            articlesConn.Open()

Assim que o objeto OdbcConnection é criado, abrimos a ligação utilizando o método Open. Esta operação pode falhar, tal como qualquer outra operação de base de dados. É por isso que todo o código de acesso à base de dados está incluído num bloco try-catch. Assim que a ligação é estabelecida, podemos executar consultas SQL na base de dados.

7.3.2.2. Executar consultas SQL

Para executar consultas SQL, precisamos de um objeto Command — mais especificamente, um objeto OdbcCommand, uma vez que estamos a utilizar uma fonte de dados ODBC. A classe OdbcCommand possui vários construtores:

  • OdbcCommand(): cria um objeto Command vazio. Para o utilizar, terá de especificar várias propriedades posteriormente:
    • CommandText: o texto da consulta SQL a ser executada
    • Connection: o objeto OdbcConnection que representa a ligação à base de dados na qual a consulta será executada
    • CommandType: o tipo da consulta SQL. Existem três valores possíveis
  1. CommandType.Text: a propriedade CommandText contém o texto de uma consulta SQL (valor padrão)
  2. CommandType.StoredProcedure: a propriedade CommandText contém o nome de um procedimento armazenado na base de dados
  3. CommandType.TableDirect: a propriedade CommandText contém o nome de uma tabela T. Equivalente a `SELECT * FROM T`. Existe apenas para controladores OLE DB.
  • OdbcCommand(string sqlText): o parâmetro sqlText será atribuído à propriedade CommandText. Este é o texto da consulta SQL a ser executada. A ligação deve ser especificada na propriedade Connection.
  • OdbcCommand(string sqlText, OdbcConnection connection): o parâmetro sqlText é atribuído à propriedade CommandText e o parâmetro connection à propriedade Connection.

Para executar a consulta SQL, estão disponíveis dois métodos:

  • OdbcDataReader ExecuteReader(): envia a consulta SELECT de CommandText para Connection e cria um objeto OdbcDataReader que fornece acesso a todas as linhas na tabela de resultados da consulta SELECT
  • int ExecuteNonQuery(): envia a consulta de atualização (INSERT, UPDATE, DELETE) de CommandText para Connection e retorna o número de linhas afetadas pela atualização.

No nosso exemplo, após abrir a ligação à base de dados, emitimos uma consulta SQL SELECT para recuperar o conteúdo da tabela ARTICLES:


            ' exécution d'une commande SQL
            Dim sqlText As String = "select * from " + tabArticles
            Dim myOdbcCommand As New OdbcCommand(sqlText)
            myOdbcCommand.Connection = articlesConn
            myReader = myOdbcCommand.ExecuteReader()

Uma consulta é normalmente do seguinte tipo:

    select col1, col2,... from table1, table2,...
    where condition
    order by expression
    ...

Apenas as palavras-chave na primeira linha são obrigatórias; as restantes são opcionais. Existem outras palavras-chave que não são apresentadas aqui.

  1. É realizada uma junção em todas as tabelas listadas após a palavra-chave `FROM`
  2. Apenas as colunas que se seguem à palavra-chave `select` são mantidas
  3. Apenas as linhas que satisfazem a condição da palavra-chave `where` são mantidas
  4. As linhas resultantes, ordenadas de acordo com a expressão na palavra-chave `ORDER BY`, formam o resultado da consulta.

O resultado de um SELECT é uma tabela. Se considerarmos a tabela ARTICLES anterior e quisermos os nomes dos artigos cujo stock atual está abaixo do limite mínimo, escreveríamos:

    select nom from articles where stock_actuel<stock_minimum

Se quisermos que sejam ordenados alfabeticamente por nome, escreveríamos:

    select nom from articles where stock_actuel<stock_minimum order by nom

7.3.2.3. Trabalhar com o resultado de uma consulta SELECT

O resultado de uma consulta SELECT no modo desconectado é um objeto DataReader, neste caso, um objeto OdbcDataReader. Este objeto permite recuperar todas as linhas do resultado sequencialmente e obter informações sobre as colunas desses resultados. Vamos examinar algumas propriedades e métodos desta classe:

FieldCount
o número de colunas na tabela
Item
Item(i) representa a coluna número i da linha atual no resultado
XXX GetXXX(i)
o valor da coluna i da linha atual, devolvido como tipo XXX (Int16, Int32, Int64, Double, String, Boolean, ...)
string GetName(i)
nome da coluna número i
Fechar()
fecha o objeto OdbcdataReader e liberta os recursos associados
bool Read()
avança uma linha na tabela de resultados. Retorna false se isso não for possível. A nova linha torna-se a linha atual do leitor.

O processamento do resultado de uma instrução SELECT é tipicamente uma operação sequencial semelhante à dos ficheiros de texto: só é possível avançar na tabela, não recuar:


While myReader.Read()
    ' on a une ligne - on l'exploite
    ....
    ' ligne suivante
end while

Estas explicações são suficientes para compreender o código seguinte no nosso exemplo:


            ' Exploitation de la table récupérée
            ' affichage des colonnes
            Dim ligne As String = ""
            Dim i As Integer
            For i = 0 To (myReader.FieldCount - 1) - 1
                ligne += myReader.GetName(i) + ","
            Next i
            ligne += myReader.GetName(i)
            Console.Out.WriteLine((ControlChars.Lf + "".PadLeft(ligne.Length, "-"c) + ControlChars.Lf + ligne + ControlChars.Lf + "".PadLeft(ligne.Length, "-"c) + ControlChars.Lf))
 
            ' affichage des données
            While myReader.Read()
                ' exploitation ligne courante
                ligne = ""
                For i = 0 To myReader.FieldCount - 1
                    ligne += myReader(i).ToString + " "
                Next i
                Console.WriteLine(ligne)
            End While

A única dificuldade reside na instrução em que os valores das diferentes colunas da linha atual são concatenados:


                For i = 0 To myReader.FieldCount - 1
                    ligne += myReader(i).ToString + " "
                Next i

A notação linha+=myReader(i).ToString é traduzida para linha+=myReader.Item(i).ToString(), onde Item(i) é o valor da coluna i na linha atual.

7.3.2.4. Libertação de recursos

As classes OdbcReader e OdbcConnection possuem ambas um método Close() que liberta os recursos associados aos objetos que estão a ser fechados.

                ' fermeture lecteur
                myReader.Close()
                ' fermeture connexion
                articlesConn.Close()

7.3.3. Utilização de um controlador OLE DB

Vamos utilizar o mesmo exemplo, desta vez com uma base de dados acedida através de um controlador OLE DB. A plataforma .NET fornece esse tipo de controlador para bases de dados Access. Por isso, vamos utilizar a mesma base de dados articles.mdb de antes. O nosso objetivo aqui é mostrar que, embora as classes possam mudar, os conceitos permanecem os mesmos:

  • a ligação é representada por um objeto OleDbConnection
  • uma consulta SQL é emitida utilizando um objeto OleDbCommand
  • se esta consulta for uma instrução SELECT, é devolvido um objeto OleDbDataReader para aceder às linhas da tabela de resultados

Estas classes encontram-se no namespace System.Data.OleDb. O programa anterior pode ser facilmente adaptado para funcionar com uma base de dados OLE DB:

  • Substitua OdbcXX por OleDbXX em todos os locais
  • modifique a cadeia de ligação. Para uma base de dados ACCESS sem nome de utilizador/palavra-passe, a cadeia de ligação é Provider=Microsoft.JET.OLEDB.4.0;Data Source=[file.mdb]. A parte configurável desta cadeia é o nome do ficheiro ACCESS a utilizar. Iremos modificar o nosso programa para que aceite o nome deste ficheiro como parâmetro.
  • O namespace a importar é agora System.Data.OleDb.

O nosso programa fica assim:


' options
Option Explicit On 
Option Strict On
 
' namespaces
Imports System
Imports System.Data
Imports Microsoft.Data.Odbc
Imports Microsoft.VisualBasic
Imports System.Data.OleDb
 
Module db2
    Public Sub Main(ByVal args() As String)
 
        ' application console
        ' displays the contents of a ARRTICLES table in a DSN database 
        ' whose name is passed in parameter
        Const syntaxe As String = "syntaxe : pg base_access_articles"
        Const tabArticles As String = "articles"        ' table of articles
 
        ' parameter verification
        ' do we have 1 parameter
        If args.Length <> 1 Then
            ' error msg
            Console.Error.WriteLine(syntaxe)
            ' end
            Environment.Exit(1)
        End If
 
        ' parameter is retrieved
        Dim dbArticles As String = args(0)        ' the database
 
        ' preparing the connection to the comic
        Dim articlesConn As OleDbConnection = Nothing        ' the connection
        Dim myReader As OleDbDataReader = Nothing        ' the data reader
 
        ' attempt to access the database
 
        Try
            ' base connection chain
            Dim connectString As String = "Provider=Microsoft.JET.OLEDB.4.0;Data Source=" + dbArticles + ";"
            articlesConn = New OleDbConnection(connectString)
            articlesConn.Open()
 
            ' execution of a SQL command
            Dim sqlText As String = "select * from " + tabArticles
            Dim myOleDbCommand As New OleDbCommand(sqlText)
            myOleDbCommand.Connection = articlesConn
            myReader = myOleDbCommand.ExecuteReader()
 
            ' Using the recovered table
            ' column display
            Dim ligne As String = ""
            Dim i As Integer
            For i = 0 To (myReader.FieldCount - 1) - 1
                ligne += myReader.GetName(i) + ","
            Next i
            ligne += myReader.GetName(i)
            Console.Out.WriteLine((ControlChars.Lf + "".PadLeft(ligne.Length, "-"c) + ControlChars.Lf + ligne + ControlChars.Lf + "".PadLeft(ligne.Length, "-"c) + ControlChars.Lf))
            ' data display
            While myReader.Read()
                ' current line operation
                ligne = ""
                For i = 0 To myReader.FieldCount - 1
                    ligne += myReader(i).ToString + " "
                Next i
                Console.WriteLine(ligne)
            End While
        Catch ex As Exception
            Console.Error.WriteLine(("Erreur d'exploitation de la base de données (" + ex.Message + ")"))
            Environment.Exit(2)
        Finally
            ' drive lock
                myReader.Close()
                ' locking connection
                articlesConn.Close()
        End Try
        ' end
        Environment.Exit(0)
    End Sub
End Module

Os resultados obtidos:

dos>vbc liste.vb

E:\data\serge\MSNET\vb.net\adonet\6>dir
07/05/2002  15:09                2 325 liste.CS
07/05/2002  15:09                4 608 liste.exe
20/08/2001  11:54               86 016 ARTICLES.MDB

dos>liste articles.mdb

----------------------------------------
code,nom,prix,stock_actuel,stock_minimum
----------------------------------------

a300 vélo                           2500 10 5
b300 pompe                          56 62 45
c300 arc                            3500 10 20
d300 flèches - lot de 6             780 12 20
e300 combinaison de plongée         2800 34 7
f300 bouteilles d'oxygène           800 10 5

7.3.4. Atualização de uma tabela

Os exemplos anteriores limitavam-se a listar o conteúdo de uma tabela. Vamos modificar o nosso programa de gestão da base de dados de produtos para que possa alterar a base de dados. O programa chama-se sql. Passamos o nome DSN da base de dados de produtos a ser gerida como parâmetro. O utilizador digita comandos SQL diretamente no teclado, que o programa executa, conforme demonstrado pelos seguintes resultados obtidos da base de dados de produtos MySQL:

dos>vbc /r:microsoft.data.odbc.dll sql.vb

dos>sql mysql-articles

Requête SQL (fin pour arrêter) : select * from articles

----------------------------------------
code,nom,prix,stock_actuel,stock_minimum
----------------------------------------

a300 vélo                           2500 10 5
b300 pompe                          56 62 45
c300 arc                            3500 10 20
d300 flèches - lot de 6             780 12 20
e300 combinaison de plongée         2800 34 7
f300 bouteilles d'oxygène           800 10 5

Requête SQL (fin pour arrêter) : select * from articles where stock_actuel<stock_minimum

----------------------------------------
code,nom,prix,stock_actuel,stock_minimum
----------------------------------------

c300 arc                            3500 10 20
d300 flèches - lot de 6             780 12 20

Requête SQL (fin pour arrêter) : insert into articles values ("1","1",1,1,1)
Il y a eu 1 ligne(s) modifiée(s)

Requête SQL (fin pour arrêter) : select * from articles

----------------------------------------
code,nom,prix,stock_actuel,stock_minimum
----------------------------------------

a300 vélo                           2500 10 5
b300 pompe                          56 62 45
c300 arc                            3500 10 20
d300 flèches - lot de 6             780 12 20
e300 combinaison de plongée         2800 34 7
f300 bouteilles d'oxygène           800 10 5
1 1 1 1 1

Requête SQL (fin pour arrêter) : update articles set nom="2" where nom="1"
Il y a eu 1 ligne(s) modifiée(s)

Requête SQL (fin pour arrêter) : select * from articles

----------------------------------------
code,nom,prix,stock_actuel,stock_minimum
----------------------------------------

a300 vélo                           2500 10 5
b300 pompe                          56 62 45
c300 arc                            3500 10 20
d300 flèches - lot de 6             780 12 20
e300 combinaison de plongée         2800 34 7
f300 bouteilles d'oxygène           800 10 5
1 2 1 1 1

Requête SQL (fin pour arrêter) : delete from articles where code="1"
Il y a eu 1 ligne(s) modifiée(s)

Requête SQL (fin pour arrêter) : select * from articles

----------------------------------------
code,nom,prix,stock_actuel,stock_minimum
----------------------------------------

a300 vélo                           2500 10 5
b300 pompe                          56 62 45
c300 arc                            3500 10 20
d300 flèches - lot de 6             780 12 20
e300 combinaison de plongée         2800 34 7
f300 bouteilles d'oxygène           800 10 5

Requête SQL (fin pour arrêter) : select * from articles order by nom asc

----------------------------------------
code,nom,prix,stock_actuel,stock_minimum
----------------------------------------

c300 arc                            3500 10 20
f300 bouteilles d'oxygène           800 10 5
e300 combinaison de plongée         2800 34 7
d300 flèches - lot de 6             780 12 20
b300 pompe                          56 62 45
a300 vélo                           2500 10 5

Requête SQL (fin pour arrêter) : fin

O programa é o seguinte:


' options
Option Explicit On 
Option Strict On

' namespaces
Imports System
Imports System.Data
Imports Microsoft.Data.Odbc
Imports System.Data.OleDb
Imports System.Text.RegularExpressions
Imports System.Collections
Imports Microsoft.VisualBasic
 
Module db3
    Public Sub Main(ByVal args() As String)
 
        ' application console
        ' executes SQL requests typed on the keyboard on a 
        ' table ARTICLES from a database DSN whose name is passed as a parameter
        Const syntaxe As String = "syntaxe : pg dsnArticles"
 
        ' parameter verification
        ' do we have 2 parameters
        If args.Length <> 1 Then
            ' error msg
            Console.Error.WriteLine(syntaxe)
            ' end
            Environment.Exit(1)
        End If        'if
        ' parameter is retrieved
        Dim dsnArticles As String = args(0)
        ' base connection chain
        Dim connectString As String = "DSN=" + dsnArticles + ";"
 
        ' preparing the connection to the comic
        Dim articlesConn As OdbcConnection = Nothing
        Dim sqlCommand As OdbcCommand = Nothing
        Try
            ' attempt to access the database
            articlesConn = New OdbcConnection(connectString)
            articlesConn.Open()
            ' create a command object
            sqlCommand = New OdbcCommand("", articlesConn)
            'try
        Catch ex As Exception
            ' error msg
            Console.Error.WriteLine(("Erreur d'exploitation de la base de données (" + ex.Message + ")"))
            ' freeing up resources
            Try
                articlesConn.Close()
            Catch
            End Try
            Environment.Exit(2)
        End Try        'catch
        ' build a dictionary of accepted sql commands
        Dim commandesSQL() As String = {"select", "insert", "update", "delete"}
        Dim dicoCommandes As New Hashtable
        Dim i As Integer
        For i = 0 To commandesSQL.Length - 1
            dicoCommandes.Add(commandesSQL(i), True)
        Next i
 
        ' read-execute SQL commands typed on the keyboard
        Dim requête As String = Nothing        ' query text SQL
        Dim champs() As String        ' query fields    
        Dim modèle As New Regex("\s+")
        ' input-execution loop for SQL commands typed on keyboard
        While True
            ' no error at start
            Dim erreur As Boolean = False
            ' request for query
            Console.Out.Write(ControlChars.Lf + "Requête SQL (fin pour arrêter) : ")
            requête = Console.In.ReadLine().Trim().ToLower()
            ' finished?
            If requête = "fin" Then
                Exit While
            End If
            ' the query is broken down into fields
            champs = modèle.Split(requête)
            ' valid request?
            If champs.Length = 0 Or Not dicoCommandes.ContainsKey(champs(0)) Then
                ' error msg
                Console.Error.WriteLine("Requête invalide. Utilisez select, insert, update, delete")
                ' following request
                erreur = True
            End If
            If Not erreur Then
                ' prepare the Command object to execute the request
                sqlCommand.CommandText = requête
                ' query execution
                Try
                    If champs(0) = "select" Then
                        executeSelect(sqlCommand)
                    Else
                        executeUpdate(sqlCommand)
                    End If
                Catch ex As Exception
                    ' error msg
                    Console.Error.WriteLine(("Erreur d'exploitation de la base de données (" + ex.Message + ")"))
                End Try
            End If
        End While
        ' freeing up resources
        Try
            articlesConn.Close()
        Catch
        End Try
        Environment.Exit(0)
    End Sub
 
    ' execute an update request
    Sub executeUpdate(ByVal sqlCommand As OdbcCommand)
        ' executes sqlCommand, update request
        Dim nbLignes As Integer = sqlCommand.ExecuteNonQuery()
        ' display
        Console.Out.WriteLine(("Il y a eu " & nbLignes & " ligne(s) modifiée(s)"))
    End Sub
 
    ' executing a Select query
    Sub executeSelect(ByVal sqlCommand As OdbcCommand)
        ' executes sqlCommand, select query
        Dim myReader As OdbcDataReader = sqlCommand.ExecuteReader()
        ' Using the recovered table
        ' column display
        Dim ligne As String = ""
        Dim i As Integer
        For i = 0 To (myReader.FieldCount - 1) - 1
            ligne += myReader.GetName(i) + ","
        Next i
        ligne += myReader.GetName(i)
        Console.Out.WriteLine((ControlChars.Lf + "".PadLeft(ligne.Length, "-"c) + ControlChars.Lf + ligne + ControlChars.Lf + "".PadLeft(ligne.Length, "-"c) + ControlChars.Lf))
        ' data display
        While myReader.Read()
            ' current line operation
            ligne = ""
            For i = 0 To myReader.FieldCount - 1
                ligne += myReader(i).ToString + " "
            Next i
            ' display
            Console.WriteLine(ligne)
        End While
        ' freeing up resources
        myReader.Close()
    End Sub
End Module

Aqui, iremos apenas comentar o que há de novo em comparação com o programa anterior:

  • Criamos um dicionário de comandos SQL aceites:

        ' on construit un dictionnaire des commandes sql acceptées
        Dim commandesSQL() As String = {"select", "insert", "update", "delete"}
        Dim dicoCommandes As New Hashtable
        Dim i As Integer
        For i = 0 To commandesSQL.Length - 1
            dicoCommandes.Add(commandesSQL(i), True)
        Next i

Isto permite-nos então verificar simplesmente se a primeira palavra (fields[0]) da consulta digitada é um dos quatro comandos aceites:


            ' requête valide ?
            If champs.Length = 0 Or Not dicoCommandes.ContainsKey(champs(0)) Then
                ' msg d'erreur
                Console.Error.WriteLine("Requête invalide. Utilisez select, insert, update, delete")
                ' requête suivante
                erreur = True
            End If            'if
  • Anteriormente, a consulta tinha sido dividida em campos utilizando o método Split da classe RegEx:

        Dim modèle As New Regex("\s+")
....
            ' the query is broken down into fields
            champs = modèle.Split(requête)

As palavras na consulta podem ser separadas por qualquer número de espaços.

  • A execução de uma consulta SELECT não utiliza o mesmo método que a execução de uma consulta de atualização (INSERT, UPDATE, DELETE). Por isso, devemos realizar uma verificação e executar uma função diferente para cada um destes dois casos:

                ' préparation de l'objet Command pour exécuter la requête
                sqlCommand.CommandText = requête
                ' exécution de la requête
                Try
                    If champs(0) = "select" Then
                        executeSelect(sqlCommand)
                    Else
                        executeUpdate(sqlCommand)
                    End If                    'try
                Catch ex As Exception
                    ' msg d'erreur
                    Console.Error.WriteLine(("Erreur d'exploitation de la base de données (" + ex.Message + ")"))
                End Try

A execução de uma consulta SQL pode gerar uma exceção, que é tratada aqui.

  • A função executeSelect abrange tudo o que foi abordado nos exemplos anteriores.
  • A função executeUpdate utiliza o método ExecuteNonQuery da classe OdbcCommand, que devolve o número de linhas afetadas pelo comando.

7.3.5. Cálculo de impostos

Reutilizamos o objeto tax criado num capítulo anterior:


' options
Option Strict On
Option Explicit On 
 
' namespaces
Imports System
 
Public Class impôt
    ' data required for tax calculation
    ' come from an external source
    Private limites(), coeffR(), coeffN() As Decimal
 
    ' manufacturer
    Public Sub New(ByVal LIMITES() As Decimal, ByVal COEFFR() As Decimal, ByVal COEFFN() As Decimal)
        ' we check that the 3 tablaeux are the same size
        Dim OK As Boolean = LIMITES.Length = COEFFR.Length And LIMITES.Length = COEFFN.Length
        If Not OK Then
            Throw New Exception("Les 3 tableaux fournis n'ont pas la même taille(" & LIMITES.Length & "," & COEFFR.Length & "," & COEFFN.Length & ")")
        End If
        ' it's good
        Me.limites = LIMITES
        Me.coeffR = COEFFR
        Me.coeffN = COEFFN
    End Sub
 
    ' tAX CALCULATION
    Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Integer) As Long
        ' calculating the number of shares
        Dim nbParts As Decimal
        If marié Then
            nbParts = CDec(nbEnfants) / 2 + 2
        Else
            nbParts = CDec(nbEnfants) / 2 + 1
        End If
        If nbEnfants >= 3 Then
            nbParts += 0.5D
        End If
        ' calculation of taxable income & family quota
        Dim revenu As Decimal = 0.72D * salaire
        Dim QF As Decimal = revenu / nbParts
        ' tAX CALCULATION
        limites((limites.Length - 1)) = QF + 1
        Dim i As Integer = 0
        While QF > limites(i)
            i += 1
        End While
        ' return result
        Return CLng(revenu * coeffR(i) - nbParts * coeffN(i))
    End Function
End Class

Adicionamos um novo construtor para inicializar as matrizes de limites, coeffR e coeffN a partir de uma base de dados ODBC:


Imports System.Data
Imports Microsoft.Data.Odbc
Imports System.Collections
...
 

    ' builder 2
    Public Sub New(ByVal DSNimpots As String, ByVal Timpots As String, ByVal colLimites As String, ByVal colCoeffR As String, ByVal colCoeffN As String)
        ' initializes the three limit arrays, coeffR, coeffN from
        ' the contents of the Timpots table in the ODBC DSNimpots database
        ' colLimites, colCoeffR, colCoeffN are the three columns of this table
        ' can throw an exception
        Dim connectString As String = "DSN=" + DSNimpots + ";"        ' base connection chain
        Dim impotsConn As OdbcConnection = Nothing        ' the connection
        Dim sqlCommand As OdbcCommand = Nothing        ' the SQL command
        ' the SELECT query
        Dim selectCommand As String = "select " + colLimites + "," + colCoeffR + "," + colCoeffN + " from " + Timpots
        ' tables to retrieve data
        Dim tLimites As New ArrayList
        Dim tCoeffR As New ArrayList
        Dim tCoeffN As New ArrayList
 
        ' attempt to access the database
        impotsConn = New OdbcConnection(connectString)
        impotsConn.Open()
        ' create a command object
        sqlCommand = New OdbcCommand(selectCommand, impotsConn)
        ' execute the query
        Dim myReader As OdbcDataReader = sqlCommand.ExecuteReader()
        ' Using the recovered table
        While myReader.Read()
            ' the data of the current line are put in the tables
            tLimites.Add(myReader(colLimites))
            tCoeffR.Add(myReader(colCoeffR))
            tCoeffN.Add(myReader(colCoeffN))
        End While
        ' freeing up resources
        myReader.Close()
        impotsConn.Close()
 
        ' dynamic tables are placed in static tables
        Me.limites = New Decimal(tLimites.Count) {}
        Me.coeffR = New Decimal(tLimites.Count) {}
        Me.coeffN = New Decimal(tLimites.Count) {}
        Dim i As Integer
        For i = 0 To tLimites.Count - 1
            limites(i) = Decimal.Parse(tLimites(i).ToString())
            coeffR(i) = Decimal.Parse(tCoeffR(i).ToString())
            coeffN(i) = Decimal.Parse(tCoeffN(i).ToString())
        Next i
    End Sub

O programa de teste é o seguinte: recebe como argumentos os parâmetros a serem passados ao construtor da classe de imposto. Após construir um objeto de imposto, calcula o imposto devido:


Option Explicit On 
Option Strict On
 
' namespaces
Imports System
Imports Microsoft.VisualBasic
 
' test pg
Module testimpots
    Sub Main(ByVal arguments() As String)
        ' interactive tax calculator
        ' the user enters three data points on the keyboard: married nbEnfants salary
        ' the program then displays the tax payable
        Const syntaxe1 As String = "pg DSNimpots tabImpots colLimites colCoeffR colCoeffN"
        Const syntaxe2 As String = "syntaxe : marié nbEnfants salaire" + ControlChars.Lf + "marié : o pour marié, n pour non marié" + ControlChars.Lf + "nbEnfants : nombre d'enfants" + ControlChars.Lf + "salaire : salaire annuel en F"
 
        ' checking program parameters
        If arguments.Length <> 5 Then
            ' error msg
            Console.Error.WriteLine(syntaxe1)
            ' end
            Environment.Exit(1)
        End If        'if
        ' retrieve the arguments
        Dim DSNimpots As String = arguments(0)
        Dim tabImpots As String = arguments(1)
        Dim colLimites As String = arguments(2)
        Dim colCoeffR As String = arguments(3)
        Dim colCoeffN As String = arguments(4)
 
        ' tax object creation
        Dim objImpôt As impôt = Nothing
        Try
            objImpôt = New impôt(DSNimpots, tabImpots, colLimites, colCoeffR, colCoeffN)
        Catch ex As Exception
            Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
            Environment.Exit(2)
        End Try
 
        ' infinite loop
        While True
            ' initially no errors
            Dim erreur As Boolean = False
 
            ' tax calculation parameters are requested
            Console.Out.Write("Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :")
            Dim paramètres As String = Console.In.ReadLine().Trim()
 
            ' anything to do?
            If paramètres Is Nothing Or paramètres = "" Then
                Exit While
            End If
 
            ' check the number of arguments in the input line
            Dim args As String() = paramètres.Split(Nothing)
            Dim nbParamètres As Integer = args.Length
            If nbParamètres <> 3 Then
                Console.Error.WriteLine(syntaxe2)
                erreur = True
            End If
            Dim marié As String
            Dim nbEnfants As Integer
            Dim salaire As Integer
            If Not erreur Then
                ' checking parameter validity
                ' married
                marié = args(0).ToLower()
                If marié <> "o" And marié <> "n" Then
                    Console.Error.WriteLine((syntaxe2 + ControlChars.Lf + "Argument marié incorrect : tapez o ou n"))
                    erreur = True
                End If
                ' nbEnfants
                nbEnfants = 0
                Try
                    nbEnfants = Integer.Parse(args(1))
                    If nbEnfants < 0 Then
                        Throw New Exception
                    End If
                Catch
                    Console.Error.WriteLine(syntaxe2 + "\nArgument nbEnfants incorrect : tapez un entier positif ou nul")
                    erreur = True
                End Try
                ' salary
                salaire = 0
                Try
                    salaire = Integer.Parse(args(2))
                    If salaire < 0 Then
                        Throw New Exception
                    End If
                Catch
                    Console.Error.WriteLine(syntaxe2 + "\nArgument salaire incorrect : tapez un entier positif ou nul")
                    erreur = True
                End Try
            End If
            If Not erreur Then
                ' parameters are correct - tax is calculated
                Console.Out.WriteLine(("impôt=" & objImpôt.calculer(marié = "o", nbEnfants, salaire).ToString + " F"))
            End If
        End While
    End Sub
End Module

A base de dados utilizada é uma base de dados MySQL com o nome DSN mysql-imports:

C:\mysql\bin>mysql --database=impots --user=admimpots --password=mdpimpots
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 5 to server version: 3.23.49-max-debug

Type 'help' for help.

mysql> show tables;
+------------------+
| Tables_in_impots |
+------------------+
| timpots          |
+------------------+

mysql> select * from timpots;
+---------+--------+---------+
| limites | coeffR | coeffN  |
+---------+--------+---------+
|   12620 |      0 |       0 |
|   13190 |   0.05 |     631 |
|   15640 |    0.1 |  1290.5 |
|   24740 |   0.15 |  2072.5 |
|   31810 |    0.2 |  3309.5 |
|   39970 |   0.25 |    4900 |
|   48360 |    0.3 |    6898 |
|   55790 |   0.35 |  9316.5 |
|   92970 |    0.4 |   12106 |
|  127860 |   0.45 |   16754 |
|  151250 |    0.5 | 23147.5 |
|  172040 |   0.55 |   30710 |
|  195000 |    0.6 |   39312 |
|       0 |   0.65 |   49062 |
+---------+--------+---------+

A execução do programa de teste produz os seguintes resultados:

dos>D:\data\devel\vbnet\poly\chap6\impots>vbc /r:system.data.dll /r:microsoft.data.odbc.dll /r:system.dll /t:library impots.vb

dos>vbc /r:impots.dll testimpots.vb

dos>test mysql-impots timpots limites coeffr coeffn
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 2 200000
impôt=22506 F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :n 2 200000
impôt=33388 F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 3 200000
impôt=16400 F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :n 3 300000
impôt=50082 F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :n 3 200000
impôt=22506 F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :