Skip to content

7. Acceso a las bases de datos

7.1. Generalidades

Existen numerosas bases de datos para las plataformas Windows. Para acceder a ellas, las aplicaciones pasan a través de programas denominados controladores (drivers).

En el esquema anterior, el controlador presenta dos interfaces:

  • la interfaz I1, que se presenta a la aplicación
  • la interfaz I2 hacia la base de datos

Para evitar que una aplicación escrita para una base de datos B1 tenga que reescribirse si se migra a una base de datos B2 diferente, se ha llevado a cabo un esfuerzo de normalización en la interfaz I1. Si se utilizan bases de datos que emplean controladores «normalizados», la base B1 se suministrará con un controlador P1, la base B2 con un controlador P2, y la interfaz I1 de estos dos controladores será idéntica. Por lo tanto, no será necesario reescribir la aplicación. De este modo, por ejemplo, se podrá migrar una base de datos ACCESS a una base de datos MySQL sin cambiar la aplicación.

Existen dos tipos de controladores estandarizados:

  • los controladores ODBC (Open DataBase Connectivity)
  • los controladores OLE y DB (Object Linking and Embedding DataBase)

Los controladores ODBC permiten el acceso a bases de datos. Las fuentes de datos para los controladores OLE y DB son más variadas: bases de datos, sistemas de mensajería, directorios... No hay límite. Cualquier fuente de datos puede ser objeto de un controlador OLE DB si así lo decide un desarrollador. El interés es, evidentemente, grande: se dispone de un acceso uniforme a una gran variedad de datos.

La plataforma .NET se suministra con dos tipos de clases de acceso a datos:

  1. las clases SQL y Server.NET
  2. las clases OLE y Db.NET

Las primeras clases permiten el acceso directo al servidor SGBD SQL de Microsoft sin controlador intermedio. Las segundas permiten el acceso a las fuentes de datos OLE DB.

Image

La plataforma .NET se suministra (mayo de 2002) con tres controladores OLE DB para, respectivamente: SQL Server, Oracle y Microsoft Jet (Access). Si se desea trabajar con una base de datos que tenga un controlador ODBC pero no un controlador OLE ni DB, no es posible. Por lo tanto, no se puede trabajar con SGBD MySQL, que (en mayo de 2002) no proporciona los controladores OLE DB. Sin embargo, existe una serie de clases que permiten el acceso a las fuentes de datos ODBC, las clases odbc.net. No se incluyen de serie con el SDK y hay que descargarlas de la página web de Microsoft. En los ejemplos que siguen, utilizaremos principalmente estas clases ODBC, ya que la mayoría de las bases de datos en Windows se suministran con un controlador de este tipo. A continuación se muestra, por ejemplo, una lista de los controladores ODBC instalados en un equipo con Windows 2000 (Menú Inicio/Configuración/Panel de control/Herramientas administrativas):

Image

Seleccionamos el icono Fuente de datos ODBC:

Image

7.2. Los dos modos de funcionamiento de una fuente de datos

La plataforma .NET permite utilizar una fuente de datos de dos maneras diferentes:

  1. modo conectado
  2. modo sin conexión

En el modo conectado, la aplicación

  1. abre una conexión con la fuente de datos
  2. trabaja con la fuente de datos en modo lectura/escritura
  3. cierra la conexión

En modo desconectado, la aplicación

  1. abre una conexión con la fuente de datos
  2. obtiene una copia en memoria de todos o parte de los datos de la fuente
  3. cierra la conexión
  4. trabaja con la copia en memoria de los datos en modo lectura/escritura
  5. cuando el trabajo ha finalizado, abre una conexión, envía los datos modificados a la fuente de datos para que los incorpore y cierra la conexión

En ambos casos, es la operación de explotación y actualización de los datos lo que lleva tiempo. Imaginemos que estas actualizaciones las realiza un usuario que introduce datos; esta operación puede llevar decenas de minutos. Durante todo ese tiempo, en modo conectado, se mantiene la conexión con la base de datos y los cambios se reflejan inmediatamente. En modo desconectado, no hay conexión con la base de datos durante la actualización de los datos. Los cambios se realizan únicamente en la copia en memoria. Se reflejan en la fuente de datos de una sola vez cuando todo ha finalizado.

¿Cuáles son las ventajas y desventajas de ambos métodos?

  • Una conexión consume muchos recursos del sistema. Si hay muchas conexiones simultáneas, el modo desconectado permite reducir su duración al mínimo. Este es el caso de las aplicaciones web con miles de usuarios.
  • La desventaja del modo desconectado es la delicada gestión de las actualizaciones simultáneas. El usuario U1 obtiene datos en el momento T1 y comienza a modificarlos. En el momento T2, el usuario U2 también accede a la fuente de datos y obtiene los mismos datos. Mientras tanto, el usuario U1 ha modificado algunos datos, pero aún no los ha transmitido a la fuente de datos. Por lo tanto, U2 trabaja con datos, algunos de los cuales son erróneos. Las clases .NET ofrecen soluciones para gestionar este problema, pero no es fácil de resolver.
  • En modo conectado, la actualización simultánea de datos por parte de varios usuarios no suele suponer ningún problema. Al mantenerse la conexión con la base de datos, es la propia base de datos la que gestiona estas actualizaciones simultáneas. Así, Oracle bloquea una línea de la base de datos en cuanto un usuario la modifica. Permanecerá bloqueada, y por tanto inaccesible para los demás usuarios, hasta que quien la haya modificado valide (commit) su modificación o la anule (rollback).
  • Si los datos deben circular por la red, hay que elegir el modo desconectado. Este permite obtener una instantánea de los datos en un objeto denominado conjunto de datos (dataset), que representa una base de datos en sí mismo. Este objeto puede circular por la red entre máquinas.

En primer lugar, estudiaremos el modo conectado.

7.3. Acceso a los datos en modo conectado

7.3.1. Las bases de datos del ejemplo

Consideramos una base de datos ACCESS llamada articles.mdb y que solo tiene una tabla llamada ARTICLES con la siguiente estructura:

nombre
tipo
código
código del artículo de 4 caracteres
nombre
su nombre (cadena de caracteres)
precio
su precio (real)
stock_actuel
su stock actual (entero)
stock_minimum
el stock mínimo (entero) por debajo del cual hay que reponer el artículo

Su contenido inicial es el siguiente:

Image

Utilizaremos esta base tanto a través de un piloto ODBC comoun controlador OLE DB para mostrar la similitud de ambos enfoques y porque disponemos de estos dos tipos de controladores para ACCESS.

También utilizaremos una base de datos MySQL DBARTICLES que contiene la misma tabla única ARTICLES, el mismo contenido y a la que se accede a través de un controlador ODBC, con el fin de demostrar que la aplicación escrita para explotar la base ACCESS no tiene que modificarse para utilizar la base MySQL. Se puede acceder a la base DBARTICLES mediante un usuario llamado admarticles con la contraseña mdparticles. La siguiente captura de pantalla muestra el contenido de la base 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 la base ACCESS como fuente de datos ODBC, siga estos pasos:

  • active el administrador de fuentes de datos ODBC tal y como se ha mostrado anteriormente y seleccione la pestaña User DSN (DSN = Nombre de la fuente de datos)

Image

  • añada una fuente con el botón Add, indique que se puede acceder a esta fuente a través de un controlador Access y haga Terminer:

Image

  • Asigne el nombre articles-access a la fuente de datos, introduzca una descripción libre y utilice el botón Sélectionner para designar el archivo .mdb de la base de datos. Finalice con OK.

Image

La nueva fuente de datos aparecerá entonces en la lista de fuentes de datos del usuario:

Image

Para definir la base de datos MySQL DBARTICLES como fuente de datos ODBC, proceda de la siguiente manera:

  • active el administrador de fuentes de datos ODBC tal y como se ha mostrado anteriormente y seleccione la pestaña Usuario DSN. Añada una nueva fuente de datos con Add y seleccione el controlador ODBC de MySQL.

Image

  • Ejecute Terminer. Aparecerá entonces una página de configuración de la fuente MySQL:

54321

Image

  • en (1) se le da un nombre a nuestra fuente de datos ODBC
  • en (2) indicamos la máquina en la que se encuentra el servidor MySQL. Aquí introducimos localhost para indicar que se encuentra en la misma máquina que nuestra aplicación. Si el servidor MySQL estuviera en una máquina M remota, introduciríamos allí su nombre y nuestra aplicación funcionaría entonces con una base de datos remota sin necesidad de modificaciones.
  • En (3) se introduce el nombre de la base de datos. Aquí se llama DBARTICLES.
  • En (4) se introduce el nombre de usuario admarticles y en (5) la contraseña mdparticles.

7.3.2. Uso de un controlador ODBC

En una aplicación que utilice una base de datos en modo conectado, normalmente se seguirán los siguientes pasos:

  1. Conexión a la base de datos
  2. Envío de consultas SQL a la base de datos
  3. Recepción y procesamiento de los resultados de estas consultas
  4. Cierre de la conexión

Los pasos 2 y 3 se repiten, y el cierre de la conexión solo tiene lugar al finalizar la explotación de la base de datos. Se trata de un esquema relativamente clásico al que quizá esté acostumbrado si ha utilizado una base de datos de forma interactiva. Estos pasos son los mismos tanto si la base de datos se utiliza a través de un controlador ODBC como de un controlador OLE DB. A continuación presentamos un ejemplo con las clases .NET de gestión de fuentes de datos ODBC. El programa se llama liste y admite como parámetro el nombre DSN de una fuente de datos ODBC que tiene una tabla ARTICLES. A continuación, muestra el contenido de esta tabla:

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

En los resultados anteriores, vemos que el programa ha listado tanto el contenido de la base ACCESS como el de la base MySQL. Analicemos ahora el código de este programa:


' opciones
Option Explicit On 
Option Strict On

' espacios de nombres
Imports System
Imports System.Data
Imports Microsoft.Data.Odbc
Imports Microsoft.VisualBasic

Module db1
    Sub main(ByVal args As String())
        ' aplicación de consola
        ' muestra el contenido de una tabla ARTICLES de una base de datos DSN 
        ' cuyo nombre se pasa como parámetro
        Const syntaxe As String = "syntaxe : pg dsnArticles"
        Const tabArticles As String = "articles"        ' la table des articles

        ' verificación de los parámetros
        ' ¿hay 1 parámetro?
        If args.Length <> 1 Then
            ' mensaje de error
            Console.Error.WriteLine(syntaxe)
            ' fin
            Environment.Exit(1)
        End If

        ' se recupera el parámetro
        Dim dsnArticles As String = args(0)                ' la base DSN
        ' Preparación de la conexión a la base de datos
        Dim articlesConn As OdbcConnection = Nothing        ' la connexion
        Dim myReader As OdbcDataReader = Nothing            ' le lecteur de données

        ' se intenta acceder a la base de datos
        Try
            ' cadena de conexión a la base
            Dim connectString As String = "DSN=" + dsnArticles + ";"
            articlesConn = New OdbcConnection(connectString)
            articlesConn.Open()

            ' ejecución de un comando SQL
            Dim sqlText As String = "select * from " + tabArticles
            Dim myOdbcCommand As New OdbcCommand(sqlText)
            myOdbcCommand.Connection = articlesConn
            myReader = myOdbcCommand.ExecuteReader()

            ' Análisis de la tabla recuperada
            ' Visualización de las columnas
            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))

            ' Visualización de los datos
            While myReader.Read()
                ' procesamiento de la línea actual
                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
            ' Cierre del lector
            myReader.Close()
            ' Cierre de la conexión
            articlesConn.Close()
        End Try
    End Sub
End Module

Las clases de gestión de fuentes ODBC se encuentran en el espacio de nombres Microsoft.Data.Odbc, por lo que debemos importarlo. Por otra parte, hay una serie de clases en el espacio de nombres System.Data.


Imports System.Data
Imports Microsoft.Data.Odbc

Los espacios de nombres utilizados por el programa se encuentran en diferentes ensamblados. Se compila el programa con el siguiente 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. La fase de conexión

Una conexión ODBC utiliza la clase OdbcConnection. El constructor de esta clase admite como parámetro lo que se denomina una cadena de conexión. Se trata de una cadena de caracteres que define todos los parámetros necesarios para que se pueda establecer la conexión con la base de datos. Estos parámetros pueden ser muy numerosos y, por lo tanto, la cadena puede resultar compleja. La cadena tiene el formato "param1=valeur1;param2=valeur2;...;paramj=valeurj;". A continuación se muestran algunos parámetros paramj posibles:

uid
nombre de un usuario que va a acceder a la base de datos
password
contraseña de este usuario
dsn
nombre de la base de datos DSN, si lo tiene
data source
nombre de la base de datos a la que se accede
...
 

Si se define una fuente de datos como ODBC mediante el administrador de fuentes de datos ODBC, estos parámetros ya se han especificado y guardado. Basta entonces con pasar el parámetro DSN, que proporciona el nombre DSN de la fuente de datos. Esto es lo que se hace aquí:


        ' preparación de la conexión a la base de datos
        Dim articlesConn As OdbcConnection = Nothing        ' la connexion
        Dim myReader As OdbcDataReader = Nothing        ' le lecteur de données
        Try
            ' se intenta acceder a la base de datos
            ' cadena de conexión a la base de datos
            Dim connectString As String = "DSN=" + dsnArticles + ";"
            articlesConn = New OdbcConnection(connectString)
            articlesConn.Open()

Una vez creado el objeto OdbcConnection, se abre la conexión con el método Open. Esta apertura puede fallar, como cualquier otra operación en la base de datos. Por eso, todo el código de acceso a la base de datos se encuentra en un try-catch. Una vez establecida la conexión, se pueden enviar consultas SQL a la base de datos.

7.3.2.2. Enviar consultas SQL

Para enviar consultas SQL, necesitamos un objeto Command, en este caso más concretamente un objeto OdbcCommand, ya que utilizamos una fuente de datos ODBC. La clase OdbcCommand tiene varios constructores:

  • OdbcCommand(): crea un objeto Command vacío. Para utilizarlo, habrá que especificar posteriormente diversas propiedades:
    • CommandText: el texto de la consulta SQL que se va a ejecutar
    • Conexión: el objeto OdbcConnection que representa la conexión a la base de datos en la que se realizará la consulta
    • CommandType: el tipo de la consulta SQL. Hay tres valores posibles
  1. CommandType.Text: la propiedad CommandText contiene el texto de una consulta SQL (valor por defecto)
  2. CommandType.StoredProcedure: la propiedad CommandText contiene el nombre de un procedimiento almacenado en la base de datos
  3. CommandType.TableDirect: la propiedad CommandText contiene el nombre de una tabla T. Equivalente a select * from T. Solo existe para los controladores OLE DB.
  • OdbcCommand(string sqlText): el parámetro sqlText se asignará a la propiedad CommandText. Este es el texto de la consulta SQL que se va a ejecutar. La conexión deberá especificarse en la propiedad Connection.
  • OdbcCommand(cadena sqlText, OdbcConnection conexión): el parámetro sqlText se asignará a la propiedad CommandText y el parámetro connexion a la propiedad Connection.

Para emitir la consulta SQL, se dispone de dos métodos:

  • OdbcdataReader ExecuteReader(): envía la consulta SELECT de CommandText a la conexión Connection y crea un objeto OdbcDataReader que permiteacceder a todas las filas de la tabla de resultados de select
  • int ExecuteNOnQuery(): envía la consulta de actualización (INSERT, UPDATE, DELETE) de CommandText a la conexión Connection y devuelve el número de líneas afectadas por esta actualización.

En nuestro ejemplo, tras abrir la conexión a la base de datos, emitimos una consulta SQL SELECT para obtener el contenido de la tabla ARTICLES:


            ' ejecución de un comando SQL
            Dim sqlText As String = "select * from " + tabArticles
            Dim myOdbcCommand As New OdbcCommand(sqlText)
            myOdbcCommand.Connection = articlesConn
            myReader = myOdbcCommand.ExecuteReader()

Una consulta de búsqueda suele ser del tipo:

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

Solo las palabras clave de la primera línea son obligatorias; las demás son opcionales. Existen otras palabras clave que no se muestran aquí.

  1. Se realiza una unión con todas las tablas que se encuentran detrás de la palabra clave from
  2. Solo se conservan las columnas que se encuentran detrás de la palabra clave «select»
  3. Solo se conservan las filas que cumplen la condición de la palabra clave «where»
  4. Las filas resultantes, ordenadas según la expresión de la palabra clave «order by», forman el resultado de la consulta.

El resultado de un select es una tabla. Si tomamos la tabla ARTICLES anterior y queremos los nombres de los artículos cuyo stock actual está por debajo del umbral mínimo, escribiremos:

    select nom from articles where stock_actuel<stock_minimum

Si los queremos ordenados alfabéticamente por nombre, escribiremos:

    select nom from articles where stock_actuel<stock_minimum order by nom

7.3.2.3. Explotación del resultado de una consulta SELECT

El resultado de una consulta SELECT en modo no conectado es un objeto DataReader, en este caso un objeto OdbcDataReader. Este objeto permite obtener secuencialmente todas las líneas del resultado y disponer de información sobre las columnas de dichos resultados. Veamos algunas propiedades y métodos de esta clase:

FieldCount
el número de columnas de la tabla
Item
Item(i) representa la columna n.º i de la línea actual del resultado
XXX GetXXX(i)
el valor de la columna n.º i de la línea actual, representado como tipo XXX (Int16, Int32, Int64, Double, String, Boolean, ...)
string GetName(i)
nombre de la columna n.º i
Close()
cierra el objeto OdbcdataReader y libera los recursos asociados
bool Read()
avanza una línea en la tabla de resultados. Devuelve falso si esto no es posible. La nueva línea se convierte en la línea actual del lector.

La explotación del resultado de un select suele ser una explotación secuencial análoga a la de los archivos de texto: solo se puede avanzar en la tabla, no retroceder:


While myReader.Read()
    ' se tiene una línea; se procesa
    ....
    ' línea siguiente
end while

Estas explicaciones bastan para comprender el siguiente código de nuestro ejemplo:


            ' Explotación de la tabla recuperada
            ' Visualización de las columnas
            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))

            ' Visualización de los datos
            While myReader.Read()
                ' explotación de la línea actual
                ligne = ""
                For i = 0 To myReader.FieldCount - 1
                    ligne += myReader(i).ToString + " "
                Next i
                Console.WriteLine(ligne)
            End While

La única dificultad está en la instrucción en la que se concatenan los valores de las diferentes columnas de la línea actual:


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

La notación línea+=myReader(i).ToString se traduce como línea+=myReader.Item(i).ToString() donde Item(i) es el valor de la columna i de la línea actual.

7.3.2.4. Liberación de recursos

Las clases OdbcReader y OdbcConnection disponen ambas de un método Close() que libera los recursos asociados a los objetos así cerrados.

                ' Cierre del lector
                myReader.Close()
                ' Cierre de la conexión
                articlesConn.Close()

7.3.3. Uso de un controlador OLE DB

Retomamos el mismo ejemplo, esta vez con una base de datos a la que se accede mediante un controlador OLE DB. La plataforma .NET proporciona un controlador de este tipo para las bases de datos ACCESS. Por lo tanto, vamos a utilizar la misma base de datos articles.mdb que anteriormente. Lo que queremos demostrar aquí es que, aunque cambien las clases, los conceptos siguen siendo los mismos:

  • la conexión está representada por un objeto OleDbConnection
  • se emite una consulta SQL gracias a un objeto OleDbCommand
  • si esta consulta es una cláusula SELECT, se obtendrá a cambio un objeto OleDbDataReader para acceder a las filas de la tabla de resultados

Estas clases se encuentran en el espacio de nombres System.Data.OleDb. El programa anterior se puede transformar fácilmente para gestionar una base OLE DB:

  • se sustituye en todas partes OdbcXX por OleDbXX
  • se modifica la cadena de conexión. Para una base de datos ACCESS sin nombre de usuario ni contraseña, la cadena de conexión es Provider=Microsoft.JET.OLEDB.4.0;Data Source=[fichier.mdb]. La parte configurable de esta cadena es el nombre del archivo ACCESS que se va a utilizar. Modificaremos nuestro programa para que acepte como parámetro el nombre de este archivo.
  • El espacio de nombres que hay que importar es ahora System.Data.OleDb.

Nuestro programa queda así:


' opciones
Option Explicit On 
Option Strict On

' espacios de nombres
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)

        ' aplicación de consola
        ' muestra el contenido de una tabla ARRTICLES de una base de datos DSN 
        ' cuyo nombre se pasa como parámetro
        Const syntaxe As String = "syntaxe : pg base_access_articles"
        Const tabArticles As String = "articles"        ' la table des articles

        ' verificación de los parámetros
        ' ¿hay 1 parámetro?
        If args.Length <> 1 Then
            ' mensaje de error
            Console.Error.WriteLine(syntaxe)
            ' fin
            Environment.Exit(1)
        End If

        ' se recupera el parámetro
        Dim dbArticles As String = args(0)        ' la base de données

        ' Preparación de la conexión a la base de datos
        Dim articlesConn As OleDbConnection = Nothing        ' la connexion
        Dim myReader As OleDbDataReader = Nothing        ' le lecteur de données

        ' se intenta acceder a la base de datos

        Try
            ' cadena de conexión a la base
            Dim connectString As String = "Provider=Microsoft.JET.OLEDB.4.0;Data Source=" + dbArticles + ";"
            articlesConn = New OleDbConnection(connectString)
            articlesConn.Open()

            ' ejecución de un comando SQL
            Dim sqlText As String = "select * from " + tabArticles
            Dim myOleDbCommand As New OleDbCommand(sqlText)
            myOleDbCommand.Connection = articlesConn
            myReader = myOleDbCommand.ExecuteReader()

            ' Análisis de la tabla recuperada
            ' Visualización de las columnas
            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))
            ' Visualización de los datos
            While myReader.Read()
                ' procesamiento de la línea actual
                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
            ' Cierre del lector
                myReader.Close()
                ' Cierre de la conexión
                articlesConn.Close()
        End Try
        ' fin
        Environment.Exit(0)
    End Sub
End Module

Resultados obtenidos:

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. Actualización de una tabla

Los ejemplos anteriores se limitaban a enumerar el contenido de una tabla. Modificamos nuestro programa de gestión de la base de datos de artículos para que pueda modificarla. El programa se llama sql. Se le pasa como parámetro el nombre DSN de la base de datos de artículos que se va a gestionar. El usuario introduce directamente los comandos SQL en el teclado y el programa los ejecuta, tal y como muestran los siguientes resultados obtenidos en la base de datos de artículos 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

El programa es el siguiente:


' opciones
Option Explicit On 
Option Strict On

' espacios de nombres
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)

        ' aplicación de consola
        ' ejecuta consultas SQL introducidas mediante el teclado en una 
        ' tabla ARTICLES de una base de datos DSN cuyo nombre se pasa como parámetro
        Const syntaxe As String = "syntaxe : pg dsnArticles"

        ' verificación de los parámetros
        ' ¿hay 2 parámetros?
        If args.Length <> 1 Then
            ' mensaje de error
            Console.Error.WriteLine(syntaxe)
            ' fin
            Environment.Exit(1)
        End If        'if
        ' se recupera el parámetro
        Dim dsnArticles As String = args(0)
        ' cadena de conexión a la base de datos
        Dim connectString As String = "DSN=" + dsnArticles + ";"

        ' preparación de la conexión a la base de datos
        Dim articlesConn As OdbcConnection = Nothing
        Dim sqlCommand As OdbcCommand = Nothing
        Try
            ' se intenta acceder a la base de datos
            articlesConn = New OdbcConnection(connectString)
            articlesConn.Open()
            ' se crea un objeto de comando
            sqlCommand = New OdbcCommand("", articlesConn)
            'try
        Catch ex As Exception
            ' mensaje de error
            Console.Error.WriteLine(("Erreur d'exploitation de la base de données (" + ex.Message + ")"))
            ' liberación de recursos
            Try
                articlesConn.Close()
            Catch
            End Try
            Environment.Exit(2)
        End Try        'catch
        ' se crea un diccionario de comandos SQL aceptados
        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

        ' lectura y ejecución de los comandos SQL introducidos mediante el teclado
        Dim requête As String = Nothing        ' texte de la requête SQL
        Dim champs() As String        ' les champs de la requête    
        Dim modèle As New Regex("\s+")
        ' bucle de introducción y ejecución de los comandos SQL introducidos mediante el teclado
        While True
            ' sin errores al inicio
            Dim erreur As Boolean = False
            ' solicitud de la consulta
            Console.Out.Write(ControlChars.Lf + "Requête SQL (fin pour arrêter) : ")
            requête = Console.In.ReadLine().Trim().ToLower()
            ' ¿Terminado?
            If requête = "fin" Then
                Exit While
            End If
            ' se descompone la consulta en campos
            champs = modèle.Split(requête)
            ' ¿consulta válida?
            If champs.Length = 0 Or Not dicoCommandes.ContainsKey(champs(0)) Then
                ' mensaje de error
                Console.Error.WriteLine("Requête invalide. Utilisez select, insert, update, delete")
                ' siguiente consulta
                erreur = True
            End If
            If Not erreur Then
                ' preparación del objeto Command para ejecutar la consulta
                sqlCommand.CommandText = requête
                ' ejecución de la consulta
                Try
                    If champs(0) = "select" Then
                        executeSelect(sqlCommand)
                    Else
                        executeUpdate(sqlCommand)
                    End If
                Catch ex As Exception
                    ' mensaje de error
                    Console.Error.WriteLine(("Erreur d'exploitation de la base de données (" + ex.Message + ")"))
                End Try
            End If
        End While
        ' liberación de recursos
        Try
            articlesConn.Close()
        Catch
        End Try
        Environment.Exit(0)
    End Sub

    ' ejecución de una consulta de actualización
    Sub executeUpdate(ByVal sqlCommand As OdbcCommand)
        ' ejecuta sqlCommand, consulta de actualización
        Dim nbLignes As Integer = sqlCommand.ExecuteNonQuery()
        ' visualización
        Console.Out.WriteLine(("Il y a eu " & nbLignes & " ligne(s) modifiée(s)"))
    End Sub

    ' ejecución de una consulta Select
    Sub executeSelect(ByVal sqlCommand As OdbcCommand)
        ' ejecuta sqlCommand, consulta Select
        Dim myReader As OdbcDataReader = sqlCommand.ExecuteReader()
        ' Explotación de la tabla recuperada
        ' visualización de las columnas
        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))
        ' Visualización de datos
        While myReader.Read()
            ' procesamiento de la línea actual
            ligne = ""
            For i = 0 To myReader.FieldCount - 1
                ligne += myReader(i).ToString + " "
            Next i
            ' Visualización
            Console.WriteLine(ligne)
        End While
        ' Liberación de recursos
        myReader.Close()
    End Sub
End Module

Aquí solo comentamos las novedades con respecto al programa anterior:

  • Creamos un diccionario de comandos SQL aceptados:

        ' se crea un diccionario de comandos SQL aceptados
        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

lo que nos permite comprobar fácilmente si la primera palabra (campo [0]) de la consulta introducida es uno de los cuatro comandos aceptados:


            ' ¿Consulta válida?
            If champs.Length = 0 Or Not dicoCommandes.ContainsKey(champs(0)) Then
                ' mensaje de error
                Console.Error.WriteLine("Requête invalide. Utilisez select, insert, update, delete")
                ' siguiente consulta
                erreur = True
            End If            'if
  • Anteriormente, la consulta se había descompuesto en campos utilizando el método Split de la clase RegEx:

        Dim modèle As New Regex("\s+")
....
            ' se descompone la consulta en campos
            champs = modèle.Split(requête)

Las palabras que componen la consulta pueden estar separadas por cualquier número de espacios.

  • La ejecución de una consulta select no utiliza el mismo método que el de una consulta de actualización (insert, update, delete). Por lo tanto, hay que realizar una prueba y ejecutar una función diferente para cada uno de estos dos casos:

                ' Preparación del objeto Command para ejecutar la consulta
                sqlCommand.CommandText = requête
                ' ejecución de la consulta
                Try
                    If champs(0) = "select" Then
                        executeSelect(sqlCommand)
                    Else
                        executeUpdate(sqlCommand)
                    End If                    'try
                Catch ex As Exception
                    ' mensaje de error
                    Console.Error.WriteLine(("Erreur d'exploitation de la base de données (" + ex.Message + ")"))
                End Try

La ejecución de una consulta SQL puede generar una excepción que se gestiona aquí.

  • La función executeSelect recoge todo lo visto en los ejemplos anteriores.
  • La función executeUpdate utiliza el método ExecuteNonQuery de la clase OdbcCommand, que devuelve el número de líneas afectadas por el comando.

7.3.5. IMPOTS

Retomamos el objeto impôt creado en un capítulo anterior:


' opciones
Option Strict On
Option Explicit On 

' espacios de nombres
Imports System

Public Class impôt
    ' datos necesarios para el cálculo del impuesto
    ' proceden de una fuente externa
    Private limites(), coeffR(), coeffN() As Decimal

    ' creador
    Public Sub New(ByVal LIMITES() As Decimal, ByVal COEFFR() As Decimal, ByVal COEFFN() As Decimal)
        ' se comprueba que las 3 tablas tengan el mismo tamaño
        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
        ' Todo correcto
        Me.limites = LIMITES
        Me.coeffR = COEFFR
        Me.coeffN = COEFFN
    End Sub

    ' cálculo del impuesto
    Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Integer) As Long
        ' cálculo del número de participaciones
        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
        ' cálculo de la renta imponible y del coeficiente familiar
        Dim revenu As Decimal = 0.72D * salaire
        Dim QF As Decimal = revenu / nbParts
        ' cálculo del impuesto
        limites((limites.Length - 1)) = QF + 1
        Dim i As Integer = 0
        While QF > limites(i)
            i += 1
        End While
        ' Devolución del resultado
        Return CLng(revenu * coeffR(i) - nbParts * coeffN(i))
    End Function
End Class

Le añadimos un nuevo constructor que permite inicializar las tablas limites, coeffR y coeffN a partir de una base de datos ODBC:


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


    ' constructor 2
    Public Sub New(ByVal DSNimpots As String, ByVal Timpots As String, ByVal colLimites As String, ByVal colCoeffR As String, ByVal colCoeffN As String)
        ' inicializa las tres tablas de límites, coeffR, coeffN a partir
        ' del contenido de la tabla Timpots de la base ODBC DSNimpots
        ' colLimites, colCoeffR, colCoeffN son las tres columnas de esta tabla
        ' puede lanzar una excepción
        Dim connectString As String = "DSN=" + DSNimpots + ";"        ' chaîne de connexion à la base
        Dim impotsConn As OdbcConnection = Nothing        ' la connexion
        Dim sqlCommand As OdbcCommand = Nothing        ' la commande SQL
        ' la consulta SELECT
        Dim selectCommand As String = "select " + colLimites + "," + colCoeffR + "," + colCoeffN + " from " + Timpots
        ' tablas para recuperar los datos
        Dim tLimites As New ArrayList
        Dim tCoeffR As New ArrayList
        Dim tCoeffN As New ArrayList

        ' se intenta acceder a la base de datos
        impotsConn = New OdbcConnection(connectString)
        impotsConn.Open()
        ' se crea un objeto de comando
        sqlCommand = New OdbcCommand(selectCommand, impotsConn)
        ' se ejecuta la consulta
        Dim myReader As OdbcDataReader = sqlCommand.ExecuteReader()
        ' Explotación de la tabla recuperada
        While myReader.Read()
            ' los datos de la línea actual se colocan en las tablas
            tLimites.Add(myReader(colLimites))
            tCoeffR.Add(myReader(colCoeffR))
            tCoeffN.Add(myReader(colCoeffN))
        End While
        ' Liberación de recursos
        myReader.Close()
        impotsConn.Close()

        ' las tablas dinámicas se colocan en tablas estáticas
        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

El programa de prueba es el siguiente: recibe como argumentos los parámetros que se deben transmitir al constructor de la clase impôt. Tras construir un objeto impôt, realiza los cálculos del impuesto a pagar:


Option Explicit On 
Option Strict On

' Espacios de nombres
Imports System
Imports Microsoft.VisualBasic

' página de prueba
Module testimpots
    Sub Main(ByVal arguments() As String)
        ' programa interactivo de cálculo de impuestos
        ' el usuario introduce tres datos con el teclado: casado nbEnfants salario
        ' el programa muestra entonces el impuesto a pagar
        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"

        ' verificación de los parámetros del programa
        If arguments.Length <> 5 Then
            ' mensaje de error
            Console.Error.WriteLine(syntaxe1)
            ' fin
            Environment.Exit(1)
        End If        'if
        ' se recuperan los argumentos
        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)

        ' creación de un objeto de impuesto
        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

        ' bucle infinito
        While True
            ' al principio no hay errores
            Dim erreur As Boolean = False

            ' se solicitan los parámetros para el cálculo del impuesto
            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()

            ' ¿Hay que hacer algo?
            If paramètres Is Nothing Or paramètres = "" Then
                Exit While
            End If

            ' Comprobación del número de argumentos en la línea introducida
            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
                ' verificación de la validez de los parámetros
                ' casado
                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
                ' salario
                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
                ' los parámetros son correctos: se calcula el impuesto
                Console.Out.WriteLine(("impôt=" & objImpôt.calculer(marié = "o", nbEnfants, salaire).ToString + " F"))
            End If
        End While
    End Sub
End Module

La base utilizada es una base MySQL con el nombre DSN mysql-impots:

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 |
+---------+--------+---------+

La ejecución del programa de prueba da los siguientes 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 :