Skip to content

7. Acceso a las bases de datos

7.1. Información general

Existen numerosas bases de datos para las plataformas de Windows. Para acceder a ellas, las aplicaciones utilizan programas llamados 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 estandarización en la interfaz I1. Si se utilizan bases de datos que emplean controladores «normalizados», la base B1 se proporcionará con un controlador P1, la base B2 con un controlador P2, y la interfaz I1 de ambos controladores será idéntica. Por lo tanto, no será necesario reescribir la aplicación. De esta manera, por ejemplo, se podrá migrar una base de datos ACCESS a una base de datos MySQL sin modificar 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, etc. No hay ningún límite. Cualquier fuente de datos puede ser objeto de un controlador OLE o DB si así lo decide el desarrollador. El beneficio es, evidentemente, enorme: se cuenta con un acceso uniforme a una gran variedad de datos.

La plataforma .NET incluye 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 de Microsoft SGBD SQL sin un controlador intermediario. 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 cuente con un controlador ODBC, pero no con los controladores OLE ni DB, no es posible. Por lo tanto, no se puede trabajar con SGBD ni MySQL, que (en mayo de 2002) no proporcionan los controladores OLE ni DB. Sin embargo, existe una serie de clases que permiten el acceso a las fuentes de datos ODBC: las clases odbc.net. Estas no se incluyen de forma predeterminada con el SDK y hay que descargarlas del sitio 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 vienen con un controlador de este tipo. A continuación, por ejemplo, se muestra una lista de los controladores ODBC instalados en una computadora con Windows 2000 (Menú Inicio/Configuración/Panel de control/Herramientas administrativas):

Image

Seleccionamos el ícono «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 modo conectado, la aplicación

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

En modo sin conexión, 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 de lectura/escritura
  5. cuando termina el trabajo, 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 procesamiento y actualización de los datos la que lleva tiempo. Imaginemos que estas actualizaciones las realiza un usuario que ingresa datos; esta operación puede tardar 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 de inmediato. 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 terminado.

¿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 sin conexión permite reducir su duración al mínimo. Este es el caso de las aplicaciones web con miles de usuarios.
  • La desventaja del modo sin conexión es la gestión complicada 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 ciertos datos, pero aún no los ha enviado 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 manejar este problema, pero no es fácil de resolver.
  • En modo conectado, la actualización simultánea de datos por parte de varios usuarios normalmente no presenta ningún problema. Dado que se mantiene 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 fila de la base de datos tan pronto como un usuario la modifica. Permanecerá bloqueada y, por lo tanto, inaccesible para los demás usuarios hasta que quien la haya modificado valide (commit) su cambio o lo deseche (rollback).
  • Si los datos deben circular por la red, se debe elegir el modo desconectado. Este permite obtener una instantánea de los datos en un objeto llamado 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, analizaremos 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 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 (número entero) por debajo del cual hay que reabastecer 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 entre ambos enfoques y porque contamos con 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 utilizar la base de datos ACCESS no necesita modificarse para utilizar la base de datos MySQL. Se puede acceder a la base de datos DBARTICLES mediante un usuario llamado admarticles con la contraseña mdparticles. La siguiente captura de pantalla muestra el contenido de la base de datos 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 como se mostró anteriormente y seleccione la pestaña Usuario DSN (DSN = Nombre de la fuente de datos)

Image

  • Agregue una fuente con el botón Add, indique que se puede acceder a esta fuente a través de un controlador Access y realice lo siguiente:

Image

  • Asigna el nombre articles-access a la fuente de datos, ingresa una descripción libre y utiliza el botón Sélectionner para seleccionar el archivo .mdb de la base de datos. Termina 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, siga estos pasos:

  • active el administrador de fuentes de datos ODBC tal como se mostró anteriormente y seleccione la pestaña «Usuario» DSN. Agrega una nueva fuente de datos con Add y selecciona el controlador ODBC de MySQL.

Image

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

54321

Image

  • en (1) le damos un nombre a nuestra fuente de datos ODBC
  • en (2) indicamos la máquina en la que se encuentra el servidor MySQL. Aquí colocamos localhost para indicar que está en la misma máquina que nuestra aplicación. Si el servidor MySQL estuviera en una máquina M remota, pondríamos ahí su nombre y nuestra aplicación funcionaría entonces con una base de datos remota sin necesidad de modificaciones.
  • En (3) se ingresa el nombre de la base de datos. Aquí se llama DBARTICLES.
  • En (4) se ingresa 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 utiliza una base de datos en modo conectado, por lo general se siguen 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 se realiza al finalizar el uso de la base de datos. Se trata de un esquema relativamente clásico con el que quizá ya esté familiarizado si ha utilizado una base de datos de manera interactiva. Estos pasos son los mismos, independientemente de si la base de datos se utiliza a través de un controlador ODBC o de un controlador OLE DB. A continuación presentamos un ejemplo con las clases .NET para la 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 contiene una tabla ARTICLES. A continuación, muestra el contenido de dicha 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 mostrado 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())
        ' consola de la aplicación
        ' 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 un 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 de datos
            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 fila 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. Además, hay varias 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 ensambles. 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 conoce como 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 posibles de paramj:

uid
nombre de un usuario que accederá a la base de datos
password
contraseña de ese usuario
dsn
nombre de la base de datos DSN, si tiene uno
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. Entonces, basta 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, al igual que 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 precisamente, 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, será necesario especificar posteriormente diversas propiedades:
    • CommandText: el texto de la consulta SQL que se 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. Equivale a select * from T. Solo existe para los controladores OLE y DB.
  • OdbcCommand(cadena sqlText): el parámetro sqlText se asignará a la propiedad CommandText. Este es el texto de la consulta SQL que se 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 ejecutar la consulta SQL, existen dos métodos:

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

En nuestro ejemplo, después de abrir la conexión a la base de datos, enviamos 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 aparecen despué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», conforman el resultado de la consulta.

El resultado de un select es una tabla. Si tomamos como referencia la tabla ARTICLES anterior y queremos obtener 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. Análisis del resultado de una consulta SELECT

El resultado de una consulta SELECT en modo sin conexión es un objeto DataReader; en este caso, un objeto OdbcDataReader. Este objeto permite obtener secuencialmente todas las filas del resultado y obtener 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 fila actual del resultado
XXX GetXXX(i)
el valor de la columna n.º i de la fila 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 similar a la de los archivos de texto: solo se puede avanzar en la tabla, no retroceder:


While myReader.Read()
    ' se tiene una fila; se procesa
    ....
     ' siguiente fila
end while

Estas explicaciones son suficientes para entender el siguiente código de nuestro ejemplo:


            ' Procesamiento de la tabla recuperada
            ' visualización de 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()
                ' análisis 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 radica en la instrucción en la que se concatenan los valores de las diferentes columnas de la fila 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 fila actual.

7.3.2.4. Liberación de recursos

Las clases OdbcReader y OdbcConnection cuentan ambas con 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, utilizaremos la misma base de datos articles.mdb que antes. Lo que queremos demostrar aquí es que, aunque las clases cambien, los conceptos siguen siendo los mismos:

  • La conexión está representada por un objeto OleDbConnection
  • Se emite una consulta SQL mediante un objeto OleDbCommand
  • si esta consulta es una cláusula SELECT, se obtendrá como respuesta 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 adaptar fácilmente para manejar una base de datos OLE DB:

  • se reemplaza 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 ahora es 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 un 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 de datos
            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 fila 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

Los 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 mostrar el contenido de una tabla. Modificamos nuestro programa de administració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 administrar. El usuario ingresa directamente los comandos SQL en el teclado, y el programa los ejecuta, tal como lo 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 ingresadas 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 dos 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 los 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 ingresados desde 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 entrada y ejecución de los comandos SQL ingresados desde 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()
        ' 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 fila 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 respecto al programa anterior:

  • Creamos un diccionario de los comandos SQL aceptados:

         ' se crea un diccionario de los 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 luego verificar fácilmente si la primera palabra (campo [0]) de la consulta ingresada 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 dividido 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, se debe 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 maneja aquí.

  • La función executeSelect resume todo lo que se ha 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
    ' provienen de una fuente externa
    Private limites(), coeffR(), coeffN() As Decimal

    ' fabricante
    Public Sub New(ByVal LIMITES() As Decimal, ByVal COEFFR() As Decimal, ByVal COEFFN() As Decimal)
        ' se verifica que las tres 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
        ' está bien
        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 partes
        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 el 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 agregamos 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 de datos ODBC DSNimpots
        ' colLimites, colCoeffR, colCoeffN son las tres columnas de esta tabla
        ' puede generar 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()
        ' Análisis de la tabla recuperada
        While myReader.Read()
            ' los datos de la fila actual se colocan en las tablas
            tLimites.Add(myReader(colLimites))
            tCoeffR.Add(myReader(colCoeffR))
            tCoeffN.Add(myReader(colCoeffN))
        End While
        ' Se liberan los recursos
        myReader.Close()
        impotsConn.Close()

        ' Las tablas dinámicas se transfieren a 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 pasar al constructor de la clase impôt. Después de crear 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 ingresa 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 inicio 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

            ' verificación del número de argumentos en la línea ingresada
            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 de datos 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 arroja 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 :